File Coverage

blib/lib/Net/DAV/LockManager.pm
Criterion Covered Total %
statement 109 111 98.2
branch 67 68 98.5
condition 38 39 97.4
subroutine 19 19 100.0
pod 7 7 100.0
total 240 244 98.3


line stmt bran cond sub pod time code
1             package Net::DAV::LockManager;
2              
3 13     13   620578 use strict;
  13         117  
  13         361  
4 13     13   65 use warnings;
  13         23  
  13         261  
5              
6 13     13   53 use File::Spec ();
  13         24  
  13         156  
7 13     13   3148 use Net::DAV::UUID;
  13         28  
  13         337  
8 13     13   3146 use Net::DAV::Lock;
  13         28  
  13         16242  
9              
10             our $VERSION = '1.304';
11             $VERSION = eval $VERSION;
12              
13             sub new {
14 55     55 1 714 my ($class, $db) = (shift, shift);
15 55         103 my %obj = @_;
16              
17 55         103 $obj{'db'} = $db;
18              
19 55         140 return bless \%obj, $class;
20             }
21              
22             sub can_modify {
23 119     119 1 7157 my ($self, $req) = @_;
24              
25 119         278 _validate_lock_request( $req, 'user' );
26              
27 100         139 my ($resource, $token) = @{$req}{qw/path token/};
  100         217  
28 100   100     203 my $lock = $self->_get_lock( $resource ) || $self->_get_indirect_lock( $resource );
29              
30 100 100       287 return 1 unless $lock;
31 42 100       138 return 0 unless $token;
32              
33 25         46 return _is_permitted( $req, $lock );
34             }
35              
36             sub lock {
37 86     86 1 13809 my ($self, $req) = @_;
38              
39 86         225 _validate_lock_request( $req, 'user', 'owner' );
40              
41 60         102 my $path = $req->{'path'};
42              
43 60 100 100     153 return undef unless $self->can_modify( $req ) && !$self->_get_lock( $path );
44 51         128 foreach my $lock ( $self->{'db'}->list_descendants( $path ) ) {
45 4 100       8 return undef unless _is_permitted( $req, $lock );
46             }
47              
48             return $self->_add_lock(Net::DAV::Lock->new({
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 48 100       334 (defined $req->{'scope'} ? ('scope' => $req->{'scope'}) : ()),
    100          
    100          
55             }));
56             }
57              
58             sub refresh_lock {
59 24     24 1 7842 my ($self, $req) = @_;
60 24         63 _validate_lock_request( $req, 'user', 'token' );
61              
62 5         11 my $lock = $self->_get_lock( $req->{'path'} );
63 5 100       14 return undef unless $lock;
64 4 100       10 return undef unless _is_permitted( $req, $lock );
65              
66 2   66     10 $lock->renew( time() + ($req->{'timeout'} || $Net::DAV::Lock::DEFAULT_LOCK_TIMEOUT) );
67              
68 2         4 return $self->_update_lock( $lock );
69             }
70              
71             sub unlock {
72 26     26 1 7578 my ($self, $req) = @_;
73 26         61 _validate_lock_request( $req, 'user', 'token' );
74              
75 7         13 my $lock = $self->_get_lock( $req->{'path'} );
76 7 100       19 return 0 unless $lock;
77 4 100       6 return 0 unless _is_permitted( $req, $lock );
78              
79 2         6 $self->_remove_lock( $lock );
80              
81 2         6 return 1;
82             }
83              
84             sub find_lock {
85 5     5 1 13 my ($self, $req) = @_;
86              
87 5         11 _validate_lock_request( $req );
88              
89 5         8 my $path = $req->{'path'};
90              
91 5   100     9 return $self->_get_lock( $path ) || $self->_get_indirect_lock( $path );
92             }
93              
94             sub list_all_locks {
95 6     6 1 16 my ($self, $req) = @_;
96              
97 6         13 _validate_lock_request( $req );
98              
99 6         10 my $path = $req->{'path'};
100 6         7 my @locks;
101 6         22 my $lock = $self->_get_lock( $path );
102 6 100       11 push @locks, $lock if defined $lock;
103              
104 6         32 while ( $path =~ s{/[^/]+$}{} ) {
105 16 100       33 $path = '/' unless length $path;
106              
107 16         25 my $lock = $self->_get_lock( $path );
108 16 100 100     46 push @locks, $lock if $lock && $lock->depth eq 'infinity';
109             }
110              
111 6         17 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   538 my ($self, $path) = @_;
121              
122 352         696 my $lock = $self->{'db'}->get( $path );
123              
124 352 100       810 return undef unless $lock;
125              
126 70 50       160 if (time() >= $lock->expiry) {
127 0         0 $self->_remove_lock($lock);
128              
129 0         0 return undef;
130             }
131              
132 70         139 return $lock;
133             }
134              
135             #
136             # Add the given lock to the database.
137             #
138             sub _add_lock {
139 48     48   115 my ($self, $lock) = @_;
140              
141 48         122 return $self->{'db'}->add($lock);
142             }
143              
144             #
145             # Update the lock provided.
146             #
147             sub _update_lock {
148 2     2   4 my ($self, $lock) = @_;
149              
150 2         5 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   3 my ($self, $lock) = @_;
158              
159 2         5 $self->{'db'}->remove($lock);
160              
161 2         2 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         400 while ( $res =~ s{/[^/]+$}{} ) {
172 161 100       321 $res = '/' unless length $res;
173              
174 161         256 my $lock = $self->_get_lock( $res );
175 161 100 100     541 return $lock if $lock && $lock->depth eq 'infinity';
176             }
177              
178 60         129 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   57 my ($req, $lock) = @_;
188              
189 37 100       81 return 0 unless $req->{'user'} eq $lock->creator;
190 24 100       61 return 0 if !defined $req->{'token'};
191 23 100       51 if ( 'ARRAY' eq ref $req->{'token'} ) {
192 7 100       9 return 0 unless grep { $_ eq $lock->token } @{$req->{'token'}};
  17         30  
  7         14  
193             }
194             else {
195 16 100       42 return 0 unless $req->{'token'} eq $lock->token;
196             }
197              
198 15         64 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   6371 my ($req, @required) = @_;
212 279 100       707 die "Parameter should be a hash reference.\n" unless 'HASH' eq ref $req;
213              
214 271         515 foreach my $arg ( qw/path/, @required ) {
215 631 100       1294 die "Missing required '$arg' parameter.\n" unless exists $req->{$arg};
216             }
217              
218 256 100       790 die "Not a clean path\n" if $req->{'path'} =~ m{(?:^|/)\.\.?(?:$|/)};
219 232 100 100     831 die "Not a clean path\n" if $req->{'path'} !~ m{^/} && !($req->{'path'} =~ s{^https?://[^/]+/}{/});
220 219 100 100     1047 if( defined $req->{'user'} && $req->{'user'} !~ m{^[0-9a-z_.][-a-z0-9_.]*$}i ) {
221 20         92 die "Not a valid user name.\n"; # May need better validation.
222             }
223              
224             # Validate optional parameters as necessary.
225 199 100 100     458 if( defined $req->{'scope'} && $Net::DAV::Lock::DEFAULT_SCOPE ne $req->{'scope'} ) {
226 2         12 die "'$req->{'scope'}' is not a supported value for scope.\n";
227             }
228              
229 197 100 100     432 if( defined $req->{'depth'} && '0' ne $req->{'depth'} && 'infinity' ne $req->{'depth'} ) {
      100        
230 2         10 die "'$req->{'depth'}' is not a supported value for depth.\n";
231             }
232              
233 195 100 100     405 if( defined $req->{'timeout'} && $req->{'timeout'} =~ /\D/ ) {
234 3         18 die "'$req->{'timeout'}' is not a supported value for timeout.\n";
235             }
236              
237 192 100       335 if ( defined $req->{'token'} ) {
238 53 100 100     143 unless ( !ref $req->{'token'} || 'ARRAY' eq ref $req->{'token'} ) {
239 1         4 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       467 $req->{'path'} =~ s{/$}{} unless $req->{'path'} eq '/';
245              
246 191         338 return;
247             }
248              
249             1;
250              
251             __END__