1<?php
2/**
3 * @copyright Copyright (c) 2016, ownCloud, Inc.
4 *
5 * @author Christoph Wurst <christoph@winzerhof-wurst.at>
6 * @author Joas Schilling <coding@schilljs.com>
7 * @author Lukas Reschke <lukas@statuscode.ch>
8 * @author Roeland Jago Douma <roeland@famdouma.nl>
9 * @author Thomas Müller <thomas.mueller@tmit.eu>
10 *
11 * @license AGPL-3.0
12 *
13 * This code is free software: you can redistribute it and/or modify
14 * it under the terms of the GNU Affero General Public License, version 3,
15 * as published by the Free Software Foundation.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU Affero General Public License for more details.
21 *
22 * You should have received a copy of the GNU Affero General Public License, version 3,
23 * along with this program. If not, see <http://www.gnu.org/licenses/>
24 *
25 */
26
27namespace dokuwiki\plugin\webdav\core\Plugin;
28
29use Sabre\DAV\INode;
30use Sabre\DAV\Locks\LockInfo;
31use Sabre\DAV\PropFind;
32use Sabre\DAV\ServerPlugin;
33use Sabre\DAV\Xml\Property\LockDiscovery;
34use Sabre\DAV\Xml\Property\SupportedLock;
35use Sabre\HTTP\RequestInterface;
36use Sabre\HTTP\ResponseInterface;
37
38/**
39 * Class FakeLockerPlugin is a plugin only used when connections come in from
40 * OS X via Finder. The fake locking plugin does emulate Class 2 WebDAV support
41 * (locking of files) which allows Finder to access the storage in write mode as
42 * well.
43 *
44 * No real locking is performed, instead the plugin just returns always positive
45 * responses.
46 *
47 * @see https://github.com/owncloud/core/issues/17732
48 */
49class FakeLocker extends ServerPlugin
50{
51    /** @var \Sabre\DAV\Server */
52    private $server;
53
54    /** {@inheritDoc} */
55    public function initialize(\Sabre\DAV\Server $server)
56    {
57        $this->server = $server;
58        $this->server->on('method:LOCK', [$this, 'fakeLockProvider'], 1);
59        $this->server->on('method:UNLOCK', [$this, 'fakeUnlockProvider'], 1);
60        $server->on('propFind', [$this, 'propFind']);
61        $server->on('validateTokens', [$this, 'validateTokens']);
62    }
63
64    /**
65     * Indicate that we support LOCK and UNLOCK
66     *
67     * @param string $path
68     * @return string[]
69     */
70    public function getHTTPMethods($path)
71    {
72        return [
73            'LOCK',
74            'UNLOCK',
75        ];
76    }
77
78    /**
79     * Indicate that we support locking
80     *
81     * @return integer[]
82     */
83    public function getFeatures()
84    {
85        return [2];
86    }
87
88    /**
89     * Return some dummy response for PROPFIND requests with regard to locking
90     *
91     * @param PropFind $propFind
92     * @param INode $node
93     * @return void
94     */
95    public function propFind(PropFind $propFind, INode $node)
96    {
97        $propFind->handle('{DAV:}supportedlock', function () {
98            return new SupportedLock(true);
99        });
100        $propFind->handle('{DAV:}lockdiscovery', function () use ($propFind) {
101            return new LockDiscovery([]);
102        });
103    }
104
105    /**
106     * Mark a locking token always as valid
107     *
108     * @param RequestInterface $request
109     * @param array $conditions
110     */
111    public function validateTokens(RequestInterface $request, &$conditions)
112    {
113        foreach ($conditions as &$fileCondition) {
114            if (isset($fileCondition['tokens'])) {
115                foreach ($fileCondition['tokens'] as &$token) {
116                    if (isset($token['token'])) {
117                        if (substr($token['token'], 0, 16) === 'opaquelocktoken:') {
118                            $token['validToken'] = true;
119                        }
120                    }
121                }
122            }
123        }
124    }
125
126    /**
127     * Fakes a successful LOCK
128     *
129     * @param RequestInterface $request
130     * @param ResponseInterface $response
131     * @return bool
132     */
133    public function fakeLockProvider(RequestInterface $request, ResponseInterface $response)
134    {
135        $lockInfo          = new LockInfo();
136        $lockInfo->token   = md5($request->getPath());
137        $lockInfo->uri     = $request->getPath();
138        $lockInfo->depth   = \Sabre\DAV\Server::DEPTH_INFINITY;
139        $lockInfo->timeout = 1800;
140
141        $body = $this->server->xml->write('{DAV:}prop', [
142            '{DAV:}lockdiscovery' => new LockDiscovery([$lockInfo]),
143        ]);
144
145        $response->setStatus(200);
146        $response->setBody($body);
147
148        return false;
149    }
150
151    /**
152     * Fakes a successful LOCK
153     *
154     * @param RequestInterface $request
155     * @param ResponseInterface $response
156     * @return bool
157     */
158    public function fakeUnlockProvider(RequestInterface $request, ResponseInterface $response)
159    {
160        $response->setStatus(204);
161        $response->setHeader('Content-Length', '0');
162        return false;
163    }
164}
165