aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/sabre/dav/lib/DAV/Locks
diff options
context:
space:
mode:
authorredmatrix <git@macgirvin.com>2016-05-10 17:26:44 -0700
committerredmatrix <git@macgirvin.com>2016-05-10 17:26:44 -0700
commit0b02a6d123b2014705998c94ddf3d460948d3eac (patch)
tree78ff2cab9944a4f5ab3f80ec93cbe1120de90bb2 /vendor/sabre/dav/lib/DAV/Locks
parent40b5b6e9d2da7ab65c8b4d38cdceac83a4d78deb (diff)
downloadvolse-hubzilla-0b02a6d123b2014705998c94ddf3d460948d3eac.tar.gz
volse-hubzilla-0b02a6d123b2014705998c94ddf3d460948d3eac.tar.bz2
volse-hubzilla-0b02a6d123b2014705998c94ddf3d460948d3eac.zip
initial sabre upgrade (needs lots of work - to wit: authentication, redo the browser interface, and rework event export/import)
Diffstat (limited to 'vendor/sabre/dav/lib/DAV/Locks')
-rw-r--r--vendor/sabre/dav/lib/DAV/Locks/Backend/AbstractBackend.php18
-rw-r--r--vendor/sabre/dav/lib/DAV/Locks/Backend/BackendInterface.php50
-rw-r--r--vendor/sabre/dav/lib/DAV/Locks/Backend/File.php185
-rw-r--r--vendor/sabre/dav/lib/DAV/Locks/Backend/PDO.php180
-rw-r--r--vendor/sabre/dav/lib/DAV/Locks/LockInfo.php80
-rw-r--r--vendor/sabre/dav/lib/DAV/Locks/Plugin.php589
6 files changed, 1102 insertions, 0 deletions
diff --git a/vendor/sabre/dav/lib/DAV/Locks/Backend/AbstractBackend.php b/vendor/sabre/dav/lib/DAV/Locks/Backend/AbstractBackend.php
new file mode 100644
index 000000000..044316cdb
--- /dev/null
+++ b/vendor/sabre/dav/lib/DAV/Locks/Backend/AbstractBackend.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Sabre\DAV\Locks\Backend;
+
+/**
+ * This is an Abstract clas for lock backends.
+ *
+ * Currently this backend has no function, but it exists for consistency, and
+ * to ensure that if default code is required in the backend, there will be a
+ * non-bc-breaking way to do so.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+abstract class AbstractBackend implements BackendInterface {
+
+}
diff --git a/vendor/sabre/dav/lib/DAV/Locks/Backend/BackendInterface.php b/vendor/sabre/dav/lib/DAV/Locks/Backend/BackendInterface.php
new file mode 100644
index 000000000..a2d2fe89c
--- /dev/null
+++ b/vendor/sabre/dav/lib/DAV/Locks/Backend/BackendInterface.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Sabre\DAV\Locks\Backend;
+
+use Sabre\DAV\Locks;
+
+/**
+ * If you are defining your own Locks backend, you must implement this
+ * interface.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+interface BackendInterface {
+
+ /**
+ * Returns a list of Sabre\DAV\Locks\LockInfo objects
+ *
+ * This method should return all the locks for a particular uri, including
+ * locks that might be set on a parent uri.
+ *
+ * If returnChildLocks is set to true, this method should also look for
+ * any locks in the subtree of the uri for locks.
+ *
+ * @param string $uri
+ * @param bool $returnChildLocks
+ * @return array
+ */
+ function getLocks($uri, $returnChildLocks);
+
+ /**
+ * Locks a uri
+ *
+ * @param string $uri
+ * @param Locks\LockInfo $lockInfo
+ * @return bool
+ */
+ function lock($uri, Locks\LockInfo $lockInfo);
+
+ /**
+ * Removes a lock from a uri
+ *
+ * @param string $uri
+ * @param Locks\LockInfo $lockInfo
+ * @return bool
+ */
+ function unlock($uri, Locks\LockInfo $lockInfo);
+
+}
diff --git a/vendor/sabre/dav/lib/DAV/Locks/Backend/File.php b/vendor/sabre/dav/lib/DAV/Locks/Backend/File.php
new file mode 100644
index 000000000..849539bee
--- /dev/null
+++ b/vendor/sabre/dav/lib/DAV/Locks/Backend/File.php
@@ -0,0 +1,185 @@
+<?php
+
+namespace Sabre\DAV\Locks\Backend;
+
+use Sabre\DAV\Locks\LockInfo;
+
+/**
+ * This Locks backend stores all locking information in a single file.
+ *
+ * Note that this is not nearly as robust as a database. If you are considering
+ * using this backend, keep in mind that the PDO backend can work with SqLite,
+ * which is designed to be a good file-based database.
+ *
+ * It literally solves the problem this class solves as well, but much better.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class File extends AbstractBackend {
+
+ /**
+ * The storage file
+ *
+ * @var string
+ */
+ private $locksFile;
+
+ /**
+ * Constructor
+ *
+ * @param string $locksFile path to file
+ */
+ function __construct($locksFile) {
+
+ $this->locksFile = $locksFile;
+
+ }
+
+ /**
+ * Returns a list of Sabre\DAV\Locks\LockInfo objects
+ *
+ * This method should return all the locks for a particular uri, including
+ * locks that might be set on a parent uri.
+ *
+ * If returnChildLocks is set to true, this method should also look for
+ * any locks in the subtree of the uri for locks.
+ *
+ * @param string $uri
+ * @param bool $returnChildLocks
+ * @return array
+ */
+ function getLocks($uri, $returnChildLocks) {
+
+ $newLocks = [];
+
+ $locks = $this->getData();
+
+ foreach ($locks as $lock) {
+
+ if ($lock->uri === $uri ||
+ //deep locks on parents
+ ($lock->depth != 0 && strpos($uri, $lock->uri . '/') === 0) ||
+
+ // locks on children
+ ($returnChildLocks && (strpos($lock->uri, $uri . '/') === 0))) {
+
+ $newLocks[] = $lock;
+
+ }
+
+ }
+
+ // Checking if we can remove any of these locks
+ foreach ($newLocks as $k => $lock) {
+ if (time() > $lock->timeout + $lock->created) unset($newLocks[$k]);
+ }
+ return $newLocks;
+
+ }
+
+ /**
+ * Locks a uri
+ *
+ * @param string $uri
+ * @param LockInfo $lockInfo
+ * @return bool
+ */
+ function lock($uri, LockInfo $lockInfo) {
+
+ // We're making the lock timeout 30 minutes
+ $lockInfo->timeout = 1800;
+ $lockInfo->created = time();
+ $lockInfo->uri = $uri;
+
+ $locks = $this->getData();
+
+ foreach ($locks as $k => $lock) {
+ if (
+ ($lock->token == $lockInfo->token) ||
+ (time() > $lock->timeout + $lock->created)
+ ) {
+ unset($locks[$k]);
+ }
+ }
+ $locks[] = $lockInfo;
+ $this->putData($locks);
+ return true;
+
+ }
+
+ /**
+ * Removes a lock from a uri
+ *
+ * @param string $uri
+ * @param LockInfo $lockInfo
+ * @return bool
+ */
+ function unlock($uri, LockInfo $lockInfo) {
+
+ $locks = $this->getData();
+ foreach ($locks as $k => $lock) {
+
+ if ($lock->token == $lockInfo->token) {
+
+ unset($locks[$k]);
+ $this->putData($locks);
+ return true;
+
+ }
+ }
+ return false;
+
+ }
+
+ /**
+ * Loads the lockdata from the filesystem.
+ *
+ * @return array
+ */
+ protected function getData() {
+
+ if (!file_exists($this->locksFile)) return [];
+
+ // opening up the file, and creating a shared lock
+ $handle = fopen($this->locksFile, 'r');
+ flock($handle, LOCK_SH);
+
+ // Reading data until the eof
+ $data = stream_get_contents($handle);
+
+ // We're all good
+ flock($handle, LOCK_UN);
+ fclose($handle);
+
+ // Unserializing and checking if the resource file contains data for this file
+ $data = unserialize($data);
+ if (!$data) return [];
+ return $data;
+
+ }
+
+ /**
+ * Saves the lockdata
+ *
+ * @param array $newData
+ * @return void
+ */
+ protected function putData(array $newData) {
+
+ // opening up the file, and creating an exclusive lock
+ $handle = fopen($this->locksFile, 'a+');
+ flock($handle, LOCK_EX);
+
+ // We can only truncate and rewind once the lock is acquired.
+ ftruncate($handle, 0);
+ rewind($handle);
+
+ fwrite($handle, serialize($newData));
+ flock($handle, LOCK_UN);
+ fclose($handle);
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/DAV/Locks/Backend/PDO.php b/vendor/sabre/dav/lib/DAV/Locks/Backend/PDO.php
new file mode 100644
index 000000000..a01d9bae4
--- /dev/null
+++ b/vendor/sabre/dav/lib/DAV/Locks/Backend/PDO.php
@@ -0,0 +1,180 @@
+<?php
+
+namespace Sabre\DAV\Locks\Backend;
+
+use Sabre\DAV\Locks\LockInfo;
+
+/**
+ * The Lock manager allows you to handle all file-locks centrally.
+ *
+ * This Lock Manager stores all its data in a database. You must pass a PDO
+ * connection object in the constructor.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class PDO extends AbstractBackend {
+
+ /**
+ * The PDO tablename this backend uses.
+ *
+ * @var string
+ */
+ public $tableName = 'locks';
+
+ /**
+ * The PDO connection object
+ *
+ * @var pdo
+ */
+ protected $pdo;
+
+ /**
+ * Constructor
+ *
+ * @param PDO $pdo
+ */
+ function __construct(\PDO $pdo) {
+
+ $this->pdo = $pdo;
+
+ }
+
+ /**
+ * Returns a list of Sabre\DAV\Locks\LockInfo objects
+ *
+ * This method should return all the locks for a particular uri, including
+ * locks that might be set on a parent uri.
+ *
+ * If returnChildLocks is set to true, this method should also look for
+ * any locks in the subtree of the uri for locks.
+ *
+ * @param string $uri
+ * @param bool $returnChildLocks
+ * @return array
+ */
+ function getLocks($uri, $returnChildLocks) {
+
+ // NOTE: the following 10 lines or so could be easily replaced by
+ // pure sql. MySQL's non-standard string concatenation prevents us
+ // from doing this though.
+ $query = 'SELECT owner, token, timeout, created, scope, depth, uri FROM ' . $this->tableName . ' WHERE (created > (? - timeout)) AND ((uri = ?)';
+ $params = [time(),$uri];
+
+ // We need to check locks for every part in the uri.
+ $uriParts = explode('/', $uri);
+
+ // We already covered the last part of the uri
+ array_pop($uriParts);
+
+ $currentPath = '';
+
+ foreach ($uriParts as $part) {
+
+ if ($currentPath) $currentPath .= '/';
+ $currentPath .= $part;
+
+ $query .= ' OR (depth!=0 AND uri = ?)';
+ $params[] = $currentPath;
+
+ }
+
+ if ($returnChildLocks) {
+
+ $query .= ' OR (uri LIKE ?)';
+ $params[] = $uri . '/%';
+
+ }
+ $query .= ')';
+
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute($params);
+ $result = $stmt->fetchAll();
+
+ $lockList = [];
+ foreach ($result as $row) {
+
+ $lockInfo = new LockInfo();
+ $lockInfo->owner = $row['owner'];
+ $lockInfo->token = $row['token'];
+ $lockInfo->timeout = $row['timeout'];
+ $lockInfo->created = $row['created'];
+ $lockInfo->scope = $row['scope'];
+ $lockInfo->depth = $row['depth'];
+ $lockInfo->uri = $row['uri'];
+ $lockList[] = $lockInfo;
+
+ }
+
+ return $lockList;
+
+ }
+
+ /**
+ * Locks a uri
+ *
+ * @param string $uri
+ * @param LockInfo $lockInfo
+ * @return bool
+ */
+ function lock($uri, LockInfo $lockInfo) {
+
+ // We're making the lock timeout 30 minutes
+ $lockInfo->timeout = 30 * 60;
+ $lockInfo->created = time();
+ $lockInfo->uri = $uri;
+
+ $locks = $this->getLocks($uri, false);
+ $exists = false;
+ foreach ($locks as $lock) {
+ if ($lock->token == $lockInfo->token) $exists = true;
+ }
+
+ if ($exists) {
+ $stmt = $this->pdo->prepare('UPDATE ' . $this->tableName . ' SET owner = ?, timeout = ?, scope = ?, depth = ?, uri = ?, created = ? WHERE token = ?');
+ $stmt->execute([
+ $lockInfo->owner,
+ $lockInfo->timeout,
+ $lockInfo->scope,
+ $lockInfo->depth,
+ $uri,
+ $lockInfo->created,
+ $lockInfo->token
+ ]);
+ } else {
+ $stmt = $this->pdo->prepare('INSERT INTO ' . $this->tableName . ' (owner,timeout,scope,depth,uri,created,token) VALUES (?,?,?,?,?,?,?)');
+ $stmt->execute([
+ $lockInfo->owner,
+ $lockInfo->timeout,
+ $lockInfo->scope,
+ $lockInfo->depth,
+ $uri,
+ $lockInfo->created,
+ $lockInfo->token
+ ]);
+ }
+
+ return true;
+
+ }
+
+
+
+ /**
+ * Removes a lock from a uri
+ *
+ * @param string $uri
+ * @param LockInfo $lockInfo
+ * @return bool
+ */
+ function unlock($uri, LockInfo $lockInfo) {
+
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->tableName . ' WHERE uri = ? AND token = ?');
+ $stmt->execute([$uri, $lockInfo->token]);
+
+ return $stmt->rowCount() === 1;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/DAV/Locks/LockInfo.php b/vendor/sabre/dav/lib/DAV/Locks/LockInfo.php
new file mode 100644
index 000000000..2c8cca0fe
--- /dev/null
+++ b/vendor/sabre/dav/lib/DAV/Locks/LockInfo.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace Sabre\DAV\Locks;
+
+/**
+ * LockInfo class
+ *
+ * An object of the LockInfo class holds all the information relevant to a
+ * single lock.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class LockInfo {
+
+ /**
+ * A shared lock
+ */
+ const SHARED = 1;
+
+ /**
+ * An exclusive lock
+ */
+ const EXCLUSIVE = 2;
+
+ /**
+ * A never expiring timeout
+ */
+ const TIMEOUT_INFINITE = -1;
+
+ /**
+ * The owner of the lock
+ *
+ * @var string
+ */
+ public $owner;
+
+ /**
+ * The locktoken
+ *
+ * @var string
+ */
+ public $token;
+
+ /**
+ * How long till the lock is expiring
+ *
+ * @var int
+ */
+ public $timeout;
+
+ /**
+ * UNIX Timestamp of when this lock was created
+ *
+ * @var int
+ */
+ public $created;
+
+ /**
+ * Exclusive or shared lock
+ *
+ * @var int
+ */
+ public $scope = self::EXCLUSIVE;
+
+ /**
+ * Depth of lock, can be 0 or Sabre\DAV\Server::DEPTH_INFINITY
+ */
+ public $depth = 0;
+
+ /**
+ * The uri this lock locks
+ *
+ * TODO: This value is not always set
+ * @var mixed
+ */
+ public $uri;
+
+}
diff --git a/vendor/sabre/dav/lib/DAV/Locks/Plugin.php b/vendor/sabre/dav/lib/DAV/Locks/Plugin.php
new file mode 100644
index 000000000..4855b7076
--- /dev/null
+++ b/vendor/sabre/dav/lib/DAV/Locks/Plugin.php
@@ -0,0 +1,589 @@
+<?php
+
+namespace Sabre\DAV\Locks;
+
+use Sabre\DAV;
+use Sabre\HTTP\RequestInterface;
+use Sabre\HTTP\ResponseInterface;
+
+/**
+ * Locking plugin
+ *
+ * This plugin provides locking support to a WebDAV server.
+ * The easiest way to get started, is by hooking it up as such:
+ *
+ * $lockBackend = new Sabre\DAV\Locks\Backend\File('./mylockdb');
+ * $lockPlugin = new Sabre\DAV\Locks\Plugin($lockBackend);
+ * $server->addPlugin($lockPlugin);
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Plugin extends DAV\ServerPlugin {
+
+ /**
+ * locksBackend
+ *
+ * @var Backend\Backend\Interface
+ */
+ protected $locksBackend;
+
+ /**
+ * server
+ *
+ * @var Sabre\DAV\Server
+ */
+ protected $server;
+
+ /**
+ * __construct
+ *
+ * @param Backend\BackendInterface $locksBackend
+ */
+ function __construct(Backend\BackendInterface $locksBackend) {
+
+ $this->locksBackend = $locksBackend;
+
+ }
+
+ /**
+ * Initializes the plugin
+ *
+ * This method is automatically called by the Server class after addPlugin.
+ *
+ * @param DAV\Server $server
+ * @return void
+ */
+ function initialize(DAV\Server $server) {
+
+ $this->server = $server;
+
+ $this->server->xml->elementMap['{DAV:}lockinfo'] = 'Sabre\\DAV\\Xml\\Request\\Lock';
+
+ $server->on('method:LOCK', [$this, 'httpLock']);
+ $server->on('method:UNLOCK', [$this, 'httpUnlock']);
+ $server->on('validateTokens', [$this, 'validateTokens']);
+ $server->on('propFind', [$this, 'propFind']);
+ $server->on('afterUnbind', [$this, 'afterUnbind']);
+
+ }
+
+ /**
+ * Returns a plugin name.
+ *
+ * Using this name other plugins will be able to access other plugins
+ * using Sabre\DAV\Server::getPlugin
+ *
+ * @return string
+ */
+ function getPluginName() {
+
+ return 'locks';
+
+ }
+
+ /**
+ * This method is called after most properties have been found
+ * it allows us to add in any Lock-related properties
+ *
+ * @param DAV\PropFind $propFind
+ * @param DAV\INode $node
+ * @return void
+ */
+ function propFind(DAV\PropFind $propFind, DAV\INode $node) {
+
+ $propFind->handle('{DAV:}supportedlock', function() {
+ return new DAV\Xml\Property\SupportedLock();
+ });
+ $propFind->handle('{DAV:}lockdiscovery', function() use ($propFind) {
+ return new DAV\Xml\Property\LockDiscovery(
+ $this->getLocks($propFind->getPath())
+ );
+ });
+
+ }
+
+ /**
+ * Use this method to tell the server this plugin defines additional
+ * HTTP methods.
+ *
+ * This method is passed a uri. It should only return HTTP methods that are
+ * available for the specified uri.
+ *
+ * @param string $uri
+ * @return array
+ */
+ function getHTTPMethods($uri) {
+
+ return ['LOCK','UNLOCK'];
+
+ }
+
+ /**
+ * Returns a list of features for the HTTP OPTIONS Dav: header.
+ *
+ * In this case this is only the number 2. The 2 in the Dav: header
+ * indicates the server supports locks.
+ *
+ * @return array
+ */
+ function getFeatures() {
+
+ return [2];
+
+ }
+
+ /**
+ * Returns all lock information on a particular uri
+ *
+ * This function should return an array with Sabre\DAV\Locks\LockInfo objects. If there are no locks on a file, return an empty array.
+ *
+ * Additionally there is also the possibility of locks on parent nodes, so we'll need to traverse every part of the tree
+ * If the $returnChildLocks argument is set to true, we'll also traverse all the children of the object
+ * for any possible locks and return those as well.
+ *
+ * @param string $uri
+ * @param bool $returnChildLocks
+ * @return array
+ */
+ function getLocks($uri, $returnChildLocks = false) {
+
+ return $this->locksBackend->getLocks($uri, $returnChildLocks);
+
+ }
+
+ /**
+ * Locks an uri
+ *
+ * The WebDAV lock request can be operated to either create a new lock on a file, or to refresh an existing lock
+ * If a new lock is created, a full XML body should be supplied, containing information about the lock such as the type
+ * of lock (shared or exclusive) and the owner of the lock
+ *
+ * If a lock is to be refreshed, no body should be supplied and there should be a valid If header containing the lock
+ *
+ * Additionally, a lock can be requested for a non-existent file. In these case we're obligated to create an empty file as per RFC4918:S7.3
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @return bool
+ */
+ function httpLock(RequestInterface $request, ResponseInterface $response) {
+
+ $uri = $request->getPath();
+
+ $existingLocks = $this->getLocks($uri);
+
+ if ($body = $request->getBodyAsString()) {
+ // This is a new lock request
+
+ $existingLock = null;
+ // Checking if there's already non-shared locks on the uri.
+ foreach ($existingLocks as $existingLock) {
+ if ($existingLock->scope === LockInfo::EXCLUSIVE) {
+ throw new DAV\Exception\ConflictingLock($existingLock);
+ }
+ }
+
+ $lockInfo = $this->parseLockRequest($body);
+ $lockInfo->depth = $this->server->getHTTPDepth();
+ $lockInfo->uri = $uri;
+ if ($existingLock && $lockInfo->scope != LockInfo::SHARED)
+ throw new DAV\Exception\ConflictingLock($existingLock);
+
+ } else {
+
+ // Gonna check if this was a lock refresh.
+ $existingLocks = $this->getLocks($uri);
+ $conditions = $this->server->getIfConditions($request);
+ $found = null;
+
+ foreach ($existingLocks as $existingLock) {
+ foreach ($conditions as $condition) {
+ foreach ($condition['tokens'] as $token) {
+ if ($token['token'] === 'opaquelocktoken:' . $existingLock->token) {
+ $found = $existingLock;
+ break 3;
+ }
+ }
+ }
+ }
+
+ // If none were found, this request is in error.
+ if (is_null($found)) {
+ if ($existingLocks) {
+ throw new DAV\Exception\Locked(reset($existingLocks));
+ } else {
+ throw new DAV\Exception\BadRequest('An xml body is required for lock requests');
+ }
+
+ }
+
+ // This must have been a lock refresh
+ $lockInfo = $found;
+
+ // The resource could have been locked through another uri.
+ if ($uri != $lockInfo->uri) $uri = $lockInfo->uri;
+
+ }
+
+ if ($timeout = $this->getTimeoutHeader()) $lockInfo->timeout = $timeout;
+
+ $newFile = false;
+
+ // 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
+ try {
+ $this->server->tree->getNodeForPath($uri);
+
+ // We need to call the beforeWriteContent event for RFC3744
+ // Edit: looks like this is not used, and causing problems now.
+ //
+ // See Issue 222
+ // $this->server->emit('beforeWriteContent',array($uri));
+
+ } catch (DAV\Exception\NotFound $e) {
+
+ // It didn't, lets create it
+ $this->server->createFile($uri, fopen('php://memory', 'r'));
+ $newFile = true;
+
+ }
+
+ $this->lockNode($uri, $lockInfo);
+
+ $response->setHeader('Content-Type', 'application/xml; charset=utf-8');
+ $response->setHeader('Lock-Token', '<opaquelocktoken:' . $lockInfo->token . '>');
+ $response->setStatus($newFile ? 201 : 200);
+ $response->setBody($this->generateLockResponse($lockInfo));
+
+ // Returning false will interupt the event chain and mark this method
+ // as 'handled'.
+ return false;
+
+ }
+
+ /**
+ * Unlocks a uri
+ *
+ * This WebDAV method allows you to remove a lock from a node. The client should provide a valid locktoken through the Lock-token http header
+ * The server should return 204 (No content) on success
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @return void
+ */
+ function httpUnlock(RequestInterface $request, ResponseInterface $response) {
+
+ $lockToken = $request->getHeader('Lock-Token');
+
+ // If the locktoken header is not supplied, we need to throw a bad request exception
+ if (!$lockToken) throw new DAV\Exception\BadRequest('No lock token was supplied');
+
+ $path = $request->getPath();
+ $locks = $this->getLocks($path);
+
+ // Windows sometimes forgets to include < and > in the Lock-Token
+ // header
+ if ($lockToken[0] !== '<') $lockToken = '<' . $lockToken . '>';
+
+ foreach ($locks as $lock) {
+
+ if ('<opaquelocktoken:' . $lock->token . '>' == $lockToken) {
+
+ $this->unlockNode($path, $lock);
+ $response->setHeader('Content-Length', '0');
+ $response->setStatus(204);
+
+ // Returning false will break the method chain, and mark the
+ // method as 'handled'.
+ return false;
+
+ }
+
+ }
+
+ // If we got here, it means the locktoken was invalid
+ throw new DAV\Exception\LockTokenMatchesRequestUri();
+
+ }
+
+ /**
+ * This method is called after a node is deleted.
+ *
+ * We use this event to clean up any locks that still exist on the node.
+ *
+ * @param string $path
+ * @return void
+ */
+ function afterUnbind($path) {
+
+ $locks = $this->getLocks($path, $includeChildren = true);
+ foreach ($locks as $lock) {
+ $this->unlockNode($path, $lock);
+ }
+
+ }
+
+ /**
+ * Locks a uri
+ *
+ * All the locking information is supplied in the lockInfo object. The object has a suggested timeout, but this can be safely ignored
+ * It is important that if the existing timeout is ignored, the property is overwritten, as this needs to be sent back to the client
+ *
+ * @param string $uri
+ * @param LockInfo $lockInfo
+ * @return bool
+ */
+ function lockNode($uri, LockInfo $lockInfo) {
+
+ if (!$this->server->emit('beforeLock', [$uri, $lockInfo])) return;
+ return $this->locksBackend->lock($uri, $lockInfo);
+
+ }
+
+ /**
+ * Unlocks a uri
+ *
+ * This method removes a lock from a uri. It is assumed all the supplied information is correct and verified
+ *
+ * @param string $uri
+ * @param LockInfo $lockInfo
+ * @return bool
+ */
+ function unlockNode($uri, LockInfo $lockInfo) {
+
+ if (!$this->server->emit('beforeUnlock', [$uri, $lockInfo])) return;
+ return $this->locksBackend->unlock($uri, $lockInfo);
+
+ }
+
+
+ /**
+ * Returns the contents of the HTTP Timeout header.
+ *
+ * The method formats the header into an integer.
+ *
+ * @return int
+ */
+ function getTimeoutHeader() {
+
+ $header = $this->server->httpRequest->getHeader('Timeout');
+
+ if ($header) {
+
+ if (stripos($header, 'second-') === 0) $header = (int)(substr($header, 7));
+ elseif (stripos($header, 'infinite') === 0) $header = LockInfo::TIMEOUT_INFINITE;
+ else throw new DAV\Exception\BadRequest('Invalid HTTP timeout header');
+
+ } else {
+
+ $header = 0;
+
+ }
+
+ return $header;
+
+ }
+
+ /**
+ * Generates the response for successful LOCK requests
+ *
+ * @param LockInfo $lockInfo
+ * @return string
+ */
+ protected function generateLockResponse(LockInfo $lockInfo) {
+
+ return $this->server->xml->write('{DAV:}prop', [
+ '{DAV:}lockdiscovery' =>
+ new DAV\Xml\Property\LockDiscovery([$lockInfo])
+ ]);
+ }
+
+ /**
+ * The validateTokens event is triggered before every request.
+ *
+ * It's a moment where this plugin can check all the supplied lock tokens
+ * in the If: header, and check if they are valid.
+ *
+ * In addition, it will also ensure that it checks any missing lokens that
+ * must be present in the request, and reject requests without the proper
+ * tokens.
+ *
+ * @param RequestInterface $request
+ * @param mixed $conditions
+ * @return void
+ */
+ function validateTokens(RequestInterface $request, &$conditions) {
+
+ // First we need to gather a list of locks that must be satisfied.
+ $mustLocks = [];
+ $method = $request->getMethod();
+
+ // Methods not in that list are operations that doesn't alter any
+ // resources, and we don't need to check the lock-states for.
+ switch ($method) {
+
+ case 'DELETE' :
+ $mustLocks = array_merge($mustLocks, $this->getLocks(
+ $request->getPath(),
+ true
+ ));
+ break;
+ case 'MKCOL' :
+ case 'MKCALENDAR' :
+ case 'PROPPATCH' :
+ case 'PUT' :
+ case 'PATCH' :
+ $mustLocks = array_merge($mustLocks, $this->getLocks(
+ $request->getPath(),
+ false
+ ));
+ break;
+ case 'MOVE' :
+ $mustLocks = array_merge($mustLocks, $this->getLocks(
+ $request->getPath(),
+ true
+ ));
+ $mustLocks = array_merge($mustLocks, $this->getLocks(
+ $this->server->calculateUri($request->getHeader('Destination')),
+ false
+ ));
+ break;
+ case 'COPY' :
+ $mustLocks = array_merge($mustLocks, $this->getLocks(
+ $this->server->calculateUri($request->getHeader('Destination')),
+ false
+ ));
+ break;
+ case 'LOCK' :
+ //Temporary measure.. figure out later why this is needed
+ // Here we basically ignore all incoming tokens...
+ foreach ($conditions as $ii => $condition) {
+ foreach ($condition['tokens'] as $jj => $token) {
+ $conditions[$ii]['tokens'][$jj]['validToken'] = true;
+ }
+ }
+ return;
+
+ }
+
+ // It's possible that there's identical locks, because of shared
+ // parents. We're removing the duplicates here.
+ $tmp = [];
+ foreach ($mustLocks as $lock) $tmp[$lock->token] = $lock;
+ $mustLocks = array_values($tmp);
+
+ foreach ($conditions as $kk => $condition) {
+
+ foreach ($condition['tokens'] as $ii => $token) {
+
+ // Lock tokens always start with opaquelocktoken:
+ if (substr($token['token'], 0, 16) !== 'opaquelocktoken:') {
+ continue;
+ }
+
+ $checkToken = substr($token['token'], 16);
+ // Looping through our list with locks.
+ foreach ($mustLocks as $jj => $mustLock) {
+
+ if ($mustLock->token == $checkToken) {
+
+ // We have a match!
+ // Removing this one from mustlocks
+ unset($mustLocks[$jj]);
+
+ // Marking the condition as valid.
+ $conditions[$kk]['tokens'][$ii]['validToken'] = true;
+
+ // Advancing to the next token
+ continue 2;
+
+ }
+
+ }
+
+ // If we got here, it means that there was a
+ // lock-token, but it was not in 'mustLocks'.
+ //
+ // This is an edge-case, as it could mean that token
+ // was specified with a url that was not 'required' to
+ // check. So we're doing one extra lookup to make sure
+ // we really don't know this token.
+ //
+ // This also gets triggered when the user specified a
+ // lock-token that was expired.
+ $oddLocks = $this->getLocks($condition['uri']);
+ foreach ($oddLocks as $oddLock) {
+
+ if ($oddLock->token === $checkToken) {
+
+ // We have a hit!
+ $conditions[$kk]['tokens'][$ii]['validToken'] = true;
+ continue 2;
+
+ }
+ }
+
+ // If we get all the way here, the lock-token was
+ // really unknown.
+
+
+ }
+
+ }
+
+ // If there's any locks left in the 'mustLocks' array, it means that
+ // the resource was locked and we must block it.
+ if ($mustLocks) {
+
+ throw new DAV\Exception\Locked(reset($mustLocks));
+
+ }
+
+ }
+
+ /**
+ * Parses a webdav lock xml body, and returns a new Sabre\DAV\Locks\LockInfo object
+ *
+ * @param string $body
+ * @return LockInfo
+ */
+ protected function parseLockRequest($body) {
+
+ $result = $this->server->xml->expect(
+ '{DAV:}lockinfo',
+ $body
+ );
+
+ $lockInfo = new LockInfo();
+
+ $lockInfo->owner = $result->owner;
+ $lockInfo->token = DAV\UUIDUtil::getUUID();
+ $lockInfo->scope = $result->scope;
+
+ return $lockInfo;
+
+ }
+
+ /**
+ * Returns a bunch of meta-data about the plugin.
+ *
+ * Providing this information is optional, and is mainly displayed by the
+ * Browser plugin.
+ *
+ * The description key in the returned array may contain html and will not
+ * be sanitized.
+ *
+ * @return array
+ */
+ function getPluginInfo() {
+
+ return [
+ 'name' => $this->getPluginName(),
+ 'description' => 'The locks plugin turns this server into a class-2 WebDAV server and adds support for LOCK and UNLOCK',
+ 'link' => 'http://sabre.io/dav/locks/',
+ ];
+
+ }
+
+}