ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
Plugin.php
Go to the documentation of this file.
1<?php
2
3namespace Sabre\DAV\Locks;
4
5use Sabre\DAV;
8
23class Plugin extends DAV\ServerPlugin {
24
30 protected $locksBackend;
31
37 protected $server;
38
44 function __construct(Backend\BackendInterface $locksBackend) {
45
46 $this->locksBackend = $locksBackend;
47
48 }
49
58 function initialize(DAV\Server $server) {
59
60 $this->server = $server;
61
62 $this->server->xml->elementMap['{DAV:}lockinfo'] = 'Sabre\\DAV\\Xml\\Request\\Lock';
63
64 $server->on('method:LOCK', [$this, 'httpLock']);
65 $server->on('method:UNLOCK', [$this, 'httpUnlock']);
66 $server->on('validateTokens', [$this, 'validateTokens']);
67 $server->on('propFind', [$this, 'propFind']);
68 $server->on('afterUnbind', [$this, 'afterUnbind']);
69
70 }
71
80 function getPluginName() {
81
82 return 'locks';
83
84 }
85
94 function propFind(DAV\PropFind $propFind, DAV\INode $node) {
95
96 $propFind->handle('{DAV:}supportedlock', function() {
98 });
99 $propFind->handle('{DAV:}lockdiscovery', function() use ($propFind) {
101 $this->getLocks($propFind->getPath())
102 );
103 });
104
105 }
106
117 function getHTTPMethods($uri) {
118
119 return ['LOCK','UNLOCK'];
120
121 }
122
131 function getFeatures() {
132
133 return [2];
134
135 }
136
150 function getLocks($uri, $returnChildLocks = false) {
151
152 return $this->locksBackend->getLocks($uri, $returnChildLocks);
153
154 }
155
172
173 $uri = $request->getPath();
174
175 $existingLocks = $this->getLocks($uri);
176
177 if ($body = $request->getBodyAsString()) {
178 // This is a new lock request
179
180 $existingLock = null;
181 // Checking if there's already non-shared locks on the uri.
182 foreach ($existingLocks as $existingLock) {
183 if ($existingLock->scope === LockInfo::EXCLUSIVE) {
184 throw new DAV\Exception\ConflictingLock($existingLock);
185 }
186 }
187
188 $lockInfo = $this->parseLockRequest($body);
189 $lockInfo->depth = $this->server->getHTTPDepth();
190 $lockInfo->uri = $uri;
191 if ($existingLock && $lockInfo->scope != LockInfo::SHARED)
192 throw new DAV\Exception\ConflictingLock($existingLock);
193
194 } else {
195
196 // Gonna check if this was a lock refresh.
197 $existingLocks = $this->getLocks($uri);
198 $conditions = $this->server->getIfConditions($request);
199 $found = null;
200
201 foreach ($existingLocks as $existingLock) {
202 foreach ($conditions as $condition) {
203 foreach ($condition['tokens'] as $token) {
204 if ($token['token'] === 'opaquelocktoken:' . $existingLock->token) {
205 $found = $existingLock;
206 break 3;
207 }
208 }
209 }
210 }
211
212 // If none were found, this request is in error.
213 if (is_null($found)) {
214 if ($existingLocks) {
215 throw new DAV\Exception\Locked(reset($existingLocks));
216 } else {
217 throw new DAV\Exception\BadRequest('An xml body is required for lock requests');
218 }
219
220 }
221
222 // This must have been a lock refresh
223 $lockInfo = $found;
224
225 // The resource could have been locked through another uri.
226 if ($uri != $lockInfo->uri) $uri = $lockInfo->uri;
227
228 }
229
230 if ($timeout = $this->getTimeoutHeader()) $lockInfo->timeout = $timeout;
231
232 $newFile = false;
233
234 // If we got this far.. we should go check if this node actually exists. If this is not the case, we need to create it first
235 try {
236 $this->server->tree->getNodeForPath($uri);
237
238 // We need to call the beforeWriteContent event for RFC3744
239 // Edit: looks like this is not used, and causing problems now.
240 //
241 // See Issue 222
242 // $this->server->emit('beforeWriteContent',array($uri));
243
244 } catch (DAV\Exception\NotFound $e) {
245
246 // It didn't, lets create it
247 $this->server->createFile($uri, fopen('php://memory', 'r'));
248 $newFile = true;
249
250 }
251
252 $this->lockNode($uri, $lockInfo);
253
254 $response->setHeader('Content-Type', 'application/xml; charset=utf-8');
255 $response->setHeader('Lock-Token', '<opaquelocktoken:' . $lockInfo->token . '>');
256 $response->setStatus($newFile ? 201 : 200);
257 $response->setBody($this->generateLockResponse($lockInfo));
258
259 // Returning false will interrupt the event chain and mark this method
260 // as 'handled'.
261 return false;
262
263 }
264
276
277 $lockToken = $request->getHeader('Lock-Token');
278
279 // If the locktoken header is not supplied, we need to throw a bad request exception
280 if (!$lockToken) throw new DAV\Exception\BadRequest('No lock token was supplied');
281
282 $path = $request->getPath();
283 $locks = $this->getLocks($path);
284
285 // Windows sometimes forgets to include < and > in the Lock-Token
286 // header
287 if ($lockToken[0] !== '<') $lockToken = '<' . $lockToken . '>';
288
289 foreach ($locks as $lock) {
290
291 if ('<opaquelocktoken:' . $lock->token . '>' == $lockToken) {
292
293 $this->unlockNode($path, $lock);
294 $response->setHeader('Content-Length', '0');
295 $response->setStatus(204);
296
297 // Returning false will break the method chain, and mark the
298 // method as 'handled'.
299 return false;
300
301 }
302
303 }
304
305 // If we got here, it means the locktoken was invalid
307
308 }
309
318 function afterUnbind($path) {
319
320 $locks = $this->getLocks($path, $includeChildren = true);
321 foreach ($locks as $lock) {
322 $this->unlockNode($path, $lock);
323 }
324
325 }
326
337 function lockNode($uri, LockInfo $lockInfo) {
338
339 if (!$this->server->emit('beforeLock', [$uri, $lockInfo])) return;
340 return $this->locksBackend->lock($uri, $lockInfo);
341
342 }
343
353 function unlockNode($uri, LockInfo $lockInfo) {
354
355 if (!$this->server->emit('beforeUnlock', [$uri, $lockInfo])) return;
356 return $this->locksBackend->unlock($uri, $lockInfo);
357
358 }
359
360
368 function getTimeoutHeader() {
369
370 $header = $this->server->httpRequest->getHeader('Timeout');
371
372 if ($header) {
373
374 if (stripos($header, 'second-') === 0) $header = (int)(substr($header, 7));
375 elseif (stripos($header, 'infinite') === 0) $header = LockInfo::TIMEOUT_INFINITE;
376 else throw new DAV\Exception\BadRequest('Invalid HTTP timeout header');
377
378 } else {
379
380 $header = 0;
381
382 }
383
384 return $header;
385
386 }
387
394 protected function generateLockResponse(LockInfo $lockInfo) {
395
396 return $this->server->xml->write('{DAV:}prop', [
397 '{DAV:}lockdiscovery' =>
398 new DAV\Xml\Property\LockDiscovery([$lockInfo])
399 ]);
400 }
401
416 function validateTokens(RequestInterface $request, &$conditions) {
417
418 // First we need to gather a list of locks that must be satisfied.
419 $mustLocks = [];
420 $method = $request->getMethod();
421
422 // Methods not in that list are operations that doesn't alter any
423 // resources, and we don't need to check the lock-states for.
424 switch ($method) {
425
426 case 'DELETE' :
427 $mustLocks = array_merge($mustLocks, $this->getLocks(
428 $request->getPath(),
429 true
430 ));
431 break;
432 case 'MKCOL' :
433 case 'MKCALENDAR' :
434 case 'PROPPATCH' :
435 case 'PUT' :
436 case 'PATCH' :
437 $mustLocks = array_merge($mustLocks, $this->getLocks(
438 $request->getPath(),
439 false
440 ));
441 break;
442 case 'MOVE' :
443 $mustLocks = array_merge($mustLocks, $this->getLocks(
444 $request->getPath(),
445 true
446 ));
447 $mustLocks = array_merge($mustLocks, $this->getLocks(
448 $this->server->calculateUri($request->getHeader('Destination')),
449 false
450 ));
451 break;
452 case 'COPY' :
453 $mustLocks = array_merge($mustLocks, $this->getLocks(
454 $this->server->calculateUri($request->getHeader('Destination')),
455 false
456 ));
457 break;
458 case 'LOCK' :
459 //Temporary measure.. figure out later why this is needed
460 // Here we basically ignore all incoming tokens...
461 foreach ($conditions as $ii => $condition) {
462 foreach ($condition['tokens'] as $jj => $token) {
463 $conditions[$ii]['tokens'][$jj]['validToken'] = true;
464 }
465 }
466 return;
467
468 }
469
470 // It's possible that there's identical locks, because of shared
471 // parents. We're removing the duplicates here.
472 $tmp = [];
473 foreach ($mustLocks as $lock) $tmp[$lock->token] = $lock;
474 $mustLocks = array_values($tmp);
475
476 foreach ($conditions as $kk => $condition) {
477
478 foreach ($condition['tokens'] as $ii => $token) {
479
480 // Lock tokens always start with opaquelocktoken:
481 if (substr($token['token'], 0, 16) !== 'opaquelocktoken:') {
482 continue;
483 }
484
485 $checkToken = substr($token['token'], 16);
486 // Looping through our list with locks.
487 foreach ($mustLocks as $jj => $mustLock) {
488
489 if ($mustLock->token == $checkToken) {
490
491 // We have a match!
492 // Removing this one from mustlocks
493 unset($mustLocks[$jj]);
494
495 // Marking the condition as valid.
496 $conditions[$kk]['tokens'][$ii]['validToken'] = true;
497
498 // Advancing to the next token
499 continue 2;
500
501 }
502
503 }
504
505 // If we got here, it means that there was a
506 // lock-token, but it was not in 'mustLocks'.
507 //
508 // This is an edge-case, as it could mean that token
509 // was specified with a url that was not 'required' to
510 // check. So we're doing one extra lookup to make sure
511 // we really don't know this token.
512 //
513 // This also gets triggered when the user specified a
514 // lock-token that was expired.
515 $oddLocks = $this->getLocks($condition['uri']);
516 foreach ($oddLocks as $oddLock) {
517
518 if ($oddLock->token === $checkToken) {
519
520 // We have a hit!
521 $conditions[$kk]['tokens'][$ii]['validToken'] = true;
522 continue 2;
523
524 }
525 }
526
527 // If we get all the way here, the lock-token was
528 // really unknown.
529
530
531 }
532
533 }
534
535 // If there's any locks left in the 'mustLocks' array, it means that
536 // the resource was locked and we must block it.
537 if ($mustLocks) {
538
539 throw new DAV\Exception\Locked(reset($mustLocks));
540
541 }
542
543 }
544
551 protected function parseLockRequest($body) {
552
553 $result = $this->server->xml->expect(
554 '{DAV:}lockinfo',
555 $body
556 );
557
558 $lockInfo = new LockInfo();
559
560 $lockInfo->owner = $result->owner;
561 $lockInfo->token = DAV\UUIDUtil::getUUID();
562 $lockInfo->scope = $result->scope;
563
564 return $lockInfo;
565
566 }
567
579 function getPluginInfo() {
580
581 return [
582 'name' => $this->getPluginName(),
583 'description' => 'The locks plugin turns this server into a class-2 WebDAV server and adds support for LOCK and UNLOCK',
584 'link' => 'http://sabre.io/dav/locks/',
585 ];
586
587 }
588
589}
$result
$path
Definition: aliased.php:25
foreach($paths as $path) $request
Definition: asyncclient.php:32
An exception for terminatinating execution or to throw for unit testing.
Main Exception class.
Definition: Exception.php:18
LockInfo class.
Definition: LockInfo.php:15
const TIMEOUT_INFINITE
A never expiring timeout.
Definition: LockInfo.php:30
const EXCLUSIVE
An exclusive lock.
Definition: LockInfo.php:25
const SHARED
A shared lock.
Definition: LockInfo.php:20
Locking plugin.
Definition: Plugin.php:23
getFeatures()
Returns a list of features for the HTTP OPTIONS Dav: header.
Definition: Plugin.php:131
getPluginName()
Returns a plugin name.
Definition: Plugin.php:80
unlockNode($uri, LockInfo $lockInfo)
Unlocks a uri.
Definition: Plugin.php:353
__construct(Backend\BackendInterface $locksBackend)
__construct
Definition: Plugin.php:44
lockNode($uri, LockInfo $lockInfo)
Locks a uri.
Definition: Plugin.php:337
httpUnlock(RequestInterface $request, ResponseInterface $response)
Unlocks a uri.
Definition: Plugin.php:275
afterUnbind($path)
This method is called after a node is deleted.
Definition: Plugin.php:318
getTimeoutHeader()
Returns the contents of the HTTP Timeout header.
Definition: Plugin.php:368
getHTTPMethods($uri)
Use this method to tell the server this plugin defines additional HTTP methods.
Definition: Plugin.php:117
httpLock(RequestInterface $request, ResponseInterface $response)
Locks an uri.
Definition: Plugin.php:171
propFind(DAV\PropFind $propFind, DAV\INode $node)
This method is called after most properties have been found it allows us to add in any Lock-related p...
Definition: Plugin.php:94
initialize(DAV\Server $server)
Initializes the plugin.
Definition: Plugin.php:58
getLocks($uri, $returnChildLocks=false)
Returns all lock information on a particular uri.
Definition: Plugin.php:150
parseLockRequest($body)
Parses a webdav lock xml body, and returns a new Sabre\DAV\Locks\LockInfo object.
Definition: Plugin.php:551
generateLockResponse(LockInfo $lockInfo)
Generates the response for successful LOCK requests.
Definition: Plugin.php:394
validateTokens(RequestInterface $request, &$conditions)
The validateTokens event is triggered before every request.
Definition: Plugin.php:416
getPluginInfo()
Returns a bunch of meta-data about the plugin.
Definition: Plugin.php:579
This class holds all the information about a PROPFIND request.
Definition: PropFind.php:11
The baseclass for all server plugins.
Main DAV server class.
Definition: Server.php:23
static getUUID()
Returns a pseudo-random v4 UUID.
Definition: UUIDUtil.php:26
Represents {DAV:}lockdiscovery property.
This class represents the {DAV:}supportedlock property.
The INode interface is the base interface, and the parent class of both ICollection and IFile.
Definition: INode.php:12
The RequestInterface represents a HTTP request.
getPath()
Returns the relative path.
This interface represents a HTTP response.
$response