Completed
Branch develop (c24f26)
by
unknown
26:13
created
htdocs/includes/sabre/sabre/dav/lib/DAV/Collection.php 1 patch
Indentation   +83 added lines, -83 removed lines patch added patch discarded remove patch
@@ -16,91 +16,91 @@
 block discarded – undo
16 16
  */
17 17
 abstract class Collection extends Node implements ICollection
18 18
 {
19
-    /**
20
-     * Returns a child object, by its name.
21
-     *
22
-     * This method makes use of the getChildren method to grab all the child
23
-     * nodes, and compares the name.
24
-     * Generally its wise to override this, as this can usually be optimized
25
-     *
26
-     * This method must throw Sabre\DAV\Exception\NotFound if the node does not
27
-     * exist.
28
-     *
29
-     * @param string $name
30
-     *
31
-     * @throws Exception\NotFound
32
-     *
33
-     * @return INode
34
-     */
35
-    public function getChild($name)
36
-    {
37
-        foreach ($this->getChildren() as $child) {
38
-            if ($child->getName() === $name) {
39
-                return $child;
40
-            }
41
-        }
42
-        throw new Exception\NotFound('File not found: '.$name);
43
-    }
19
+	/**
20
+	 * Returns a child object, by its name.
21
+	 *
22
+	 * This method makes use of the getChildren method to grab all the child
23
+	 * nodes, and compares the name.
24
+	 * Generally its wise to override this, as this can usually be optimized
25
+	 *
26
+	 * This method must throw Sabre\DAV\Exception\NotFound if the node does not
27
+	 * exist.
28
+	 *
29
+	 * @param string $name
30
+	 *
31
+	 * @throws Exception\NotFound
32
+	 *
33
+	 * @return INode
34
+	 */
35
+	public function getChild($name)
36
+	{
37
+		foreach ($this->getChildren() as $child) {
38
+			if ($child->getName() === $name) {
39
+				return $child;
40
+			}
41
+		}
42
+		throw new Exception\NotFound('File not found: '.$name);
43
+	}
44 44
 
45
-    /**
46
-     * Checks is a child-node exists.
47
-     *
48
-     * It is generally a good idea to try and override this. Usually it can be optimized.
49
-     *
50
-     * @param string $name
51
-     *
52
-     * @return bool
53
-     */
54
-    public function childExists($name)
55
-    {
56
-        try {
57
-            $this->getChild($name);
45
+	/**
46
+	 * Checks is a child-node exists.
47
+	 *
48
+	 * It is generally a good idea to try and override this. Usually it can be optimized.
49
+	 *
50
+	 * @param string $name
51
+	 *
52
+	 * @return bool
53
+	 */
54
+	public function childExists($name)
55
+	{
56
+		try {
57
+			$this->getChild($name);
58 58
 
59
-            return true;
60
-        } catch (Exception\NotFound $e) {
61
-            return false;
62
-        }
63
-    }
59
+			return true;
60
+		} catch (Exception\NotFound $e) {
61
+			return false;
62
+		}
63
+	}
64 64
 
65
-    /**
66
-     * Creates a new file in the directory.
67
-     *
68
-     * Data will either be supplied as a stream resource, or in certain cases
69
-     * as a string. Keep in mind that you may have to support either.
70
-     *
71
-     * After successful creation of the file, you may choose to return the ETag
72
-     * of the new file here.
73
-     *
74
-     * The returned ETag must be surrounded by double-quotes (The quotes should
75
-     * be part of the actual string).
76
-     *
77
-     * If you cannot accurately determine the ETag, you should not return it.
78
-     * If you don't store the file exactly as-is (you're transforming it
79
-     * somehow) you should also not return an ETag.
80
-     *
81
-     * This means that if a subsequent GET to this new file does not exactly
82
-     * return the same contents of what was submitted here, you are strongly
83
-     * recommended to omit the ETag.
84
-     *
85
-     * @param string          $name Name of the file
86
-     * @param resource|string $data Initial payload
87
-     *
88
-     * @return string|null
89
-     */
90
-    public function createFile($name, $data = null)
91
-    {
92
-        throw new Exception\Forbidden('Permission denied to create file (filename '.$name.')');
93
-    }
65
+	/**
66
+	 * Creates a new file in the directory.
67
+	 *
68
+	 * Data will either be supplied as a stream resource, or in certain cases
69
+	 * as a string. Keep in mind that you may have to support either.
70
+	 *
71
+	 * After successful creation of the file, you may choose to return the ETag
72
+	 * of the new file here.
73
+	 *
74
+	 * The returned ETag must be surrounded by double-quotes (The quotes should
75
+	 * be part of the actual string).
76
+	 *
77
+	 * If you cannot accurately determine the ETag, you should not return it.
78
+	 * If you don't store the file exactly as-is (you're transforming it
79
+	 * somehow) you should also not return an ETag.
80
+	 *
81
+	 * This means that if a subsequent GET to this new file does not exactly
82
+	 * return the same contents of what was submitted here, you are strongly
83
+	 * recommended to omit the ETag.
84
+	 *
85
+	 * @param string          $name Name of the file
86
+	 * @param resource|string $data Initial payload
87
+	 *
88
+	 * @return string|null
89
+	 */
90
+	public function createFile($name, $data = null)
91
+	{
92
+		throw new Exception\Forbidden('Permission denied to create file (filename '.$name.')');
93
+	}
94 94
 
95
-    /**
96
-     * Creates a new subdirectory.
97
-     *
98
-     * @param string $name
99
-     *
100
-     * @throws Exception\Forbidden
101
-     */
102
-    public function createDirectory($name)
103
-    {
104
-        throw new Exception\Forbidden('Permission denied to create directory');
105
-    }
95
+	/**
96
+	 * Creates a new subdirectory.
97
+	 *
98
+	 * @param string $name
99
+	 *
100
+	 * @throws Exception\Forbidden
101
+	 */
102
+	public function createDirectory($name)
103
+	{
104
+		throw new Exception\Forbidden('Permission denied to create directory');
105
+	}
106 106
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/DAV/TemporaryFileFilterPlugin.php 2 patches
Indentation   +261 added lines, -261 removed lines patch added patch discarded remove patch
@@ -34,265 +34,265 @@
 block discarded – undo
34 34
  */
35 35
 class TemporaryFileFilterPlugin extends ServerPlugin
36 36
 {
37
-    /**
38
-     * This is the list of patterns we intercept.
39
-     * If new patterns are added, they must be valid patterns for preg_match.
40
-     *
41
-     * @var array
42
-     */
43
-    public $temporaryFilePatterns = [
44
-        '/^\._(.*)$/',     // OS/X resource forks
45
-        '/^.DS_Store$/',   // OS/X custom folder settings
46
-        '/^desktop.ini$/', // Windows custom folder settings
47
-        '/^Thumbs.db$/',   // Windows thumbnail cache
48
-        '/^.(.*).swp$/',   // ViM temporary files
49
-        '/^\.dat(.*)$/',   // Smultron seems to create these
50
-        '/^~lock.(.*)#$/', // Windows 7 lockfiles
51
-    ];
52
-
53
-    /**
54
-     * A reference to the main Server class.
55
-     *
56
-     * @var \Sabre\DAV\Server
57
-     */
58
-    protected $server;
59
-
60
-    /**
61
-     * This is the directory where this plugin
62
-     * will store it's files.
63
-     *
64
-     * @var string
65
-     */
66
-    private $dataDir;
67
-
68
-    /**
69
-     * Creates the plugin.
70
-     *
71
-     * Make sure you specify a directory for your files. If you don't, we
72
-     * will use PHP's directory for session-storage instead, and you might
73
-     * not want that.
74
-     *
75
-     * @param string|null $dataDir
76
-     */
77
-    public function __construct($dataDir = null)
78
-    {
79
-        if (!$dataDir) {
80
-            $dataDir = ini_get('session.save_path').'/sabredav/';
81
-        }
82
-        if (!is_dir($dataDir)) {
83
-            mkdir($dataDir);
84
-        }
85
-        $this->dataDir = $dataDir;
86
-    }
87
-
88
-    /**
89
-     * Initialize the plugin.
90
-     *
91
-     * This is called automatically be the Server class after this plugin is
92
-     * added with Sabre\DAV\Server::addPlugin()
93
-     */
94
-    public function initialize(Server $server)
95
-    {
96
-        $this->server = $server;
97
-        $server->on('beforeMethod:*', [$this, 'beforeMethod']);
98
-        $server->on('beforeCreateFile', [$this, 'beforeCreateFile']);
99
-    }
100
-
101
-    /**
102
-     * This method is called before any HTTP method handler.
103
-     *
104
-     * This method intercepts any GET, DELETE, PUT and PROPFIND calls to
105
-     * filenames that are known to match the 'temporary file' regex.
106
-     *
107
-     * @return bool
108
-     */
109
-    public function beforeMethod(RequestInterface $request, ResponseInterface $response)
110
-    {
111
-        if (!$tempLocation = $this->isTempFile($request->getPath())) {
112
-            return;
113
-        }
114
-
115
-        switch ($request->getMethod()) {
116
-            case 'GET':
117
-                return $this->httpGet($request, $response, $tempLocation);
118
-            case 'PUT':
119
-                return $this->httpPut($request, $response, $tempLocation);
120
-            case 'PROPFIND':
121
-                return $this->httpPropfind($request, $response, $tempLocation);
122
-            case 'DELETE':
123
-                return $this->httpDelete($request, $response, $tempLocation);
124
-        }
125
-
126
-        return;
127
-    }
128
-
129
-    /**
130
-     * This method is invoked if some subsystem creates a new file.
131
-     *
132
-     * This is used to deal with HTTP LOCK requests which create a new
133
-     * file.
134
-     *
135
-     * @param string   $uri
136
-     * @param resource $data
137
-     * @param bool     $modified should be set to true, if this event handler
138
-     *                           changed &$data
139
-     *
140
-     * @return bool
141
-     */
142
-    public function beforeCreateFile($uri, $data, ICollection $parent, $modified)
143
-    {
144
-        if ($tempPath = $this->isTempFile($uri)) {
145
-            $hR = $this->server->httpResponse;
146
-            $hR->setHeader('X-Sabre-Temp', 'true');
147
-            file_put_contents($tempPath, $data);
148
-
149
-            return false;
150
-        }
151
-
152
-        return;
153
-    }
154
-
155
-    /**
156
-     * This method will check if the url matches the temporary file pattern
157
-     * if it does, it will return an path based on $this->dataDir for the
158
-     * temporary file storage.
159
-     *
160
-     * @param string $path
161
-     *
162
-     * @return bool|string
163
-     */
164
-    protected function isTempFile($path)
165
-    {
166
-        // We're only interested in the basename.
167
-        list(, $tempPath) = Uri\split($path);
168
-
169
-        if (null === $tempPath) {
170
-            return false;
171
-        }
172
-
173
-        foreach ($this->temporaryFilePatterns as $tempFile) {
174
-            if (preg_match($tempFile, $tempPath)) {
175
-                return $this->getDataDir().'/sabredav_'.md5($path).'.tempfile';
176
-            }
177
-        }
178
-
179
-        return false;
180
-    }
181
-
182
-    /**
183
-     * This method handles the GET method for temporary files.
184
-     * If the file doesn't exist, it will return false which will kick in
185
-     * the regular system for the GET method.
186
-     *
187
-     * @param string $tempLocation
188
-     *
189
-     * @return bool
190
-     */
191
-    public function httpGet(RequestInterface $request, ResponseInterface $hR, $tempLocation)
192
-    {
193
-        if (!file_exists($tempLocation)) {
194
-            return;
195
-        }
196
-
197
-        $hR->setHeader('Content-Type', 'application/octet-stream');
198
-        $hR->setHeader('Content-Length', filesize($tempLocation));
199
-        $hR->setHeader('X-Sabre-Temp', 'true');
200
-        $hR->setStatus(200);
201
-        $hR->setBody(fopen($tempLocation, 'r'));
202
-
203
-        return false;
204
-    }
205
-
206
-    /**
207
-     * This method handles the PUT method.
208
-     *
209
-     * @param string $tempLocation
210
-     *
211
-     * @return bool
212
-     */
213
-    public function httpPut(RequestInterface $request, ResponseInterface $hR, $tempLocation)
214
-    {
215
-        $hR->setHeader('X-Sabre-Temp', 'true');
216
-
217
-        $newFile = !file_exists($tempLocation);
218
-
219
-        if (!$newFile && ($this->server->httpRequest->getHeader('If-None-Match'))) {
220
-            throw new Exception\PreconditionFailed('The resource already exists, and an If-None-Match header was supplied');
221
-        }
222
-
223
-        file_put_contents($tempLocation, $this->server->httpRequest->getBody());
224
-        $hR->setStatus($newFile ? 201 : 200);
225
-
226
-        return false;
227
-    }
228
-
229
-    /**
230
-     * This method handles the DELETE method.
231
-     *
232
-     * If the file didn't exist, it will return false, which will make the
233
-     * standard HTTP DELETE handler kick in.
234
-     *
235
-     * @param string $tempLocation
236
-     *
237
-     * @return bool
238
-     */
239
-    public function httpDelete(RequestInterface $request, ResponseInterface $hR, $tempLocation)
240
-    {
241
-        if (!file_exists($tempLocation)) {
242
-            return;
243
-        }
244
-
245
-        unlink($tempLocation);
246
-        $hR->setHeader('X-Sabre-Temp', 'true');
247
-        $hR->setStatus(204);
248
-
249
-        return false;
250
-    }
251
-
252
-    /**
253
-     * This method handles the PROPFIND method.
254
-     *
255
-     * It's a very lazy method, it won't bother checking the request body
256
-     * for which properties were requested, and just sends back a default
257
-     * set of properties.
258
-     *
259
-     * @param string $tempLocation
260
-     *
261
-     * @return bool
262
-     */
263
-    public function httpPropfind(RequestInterface $request, ResponseInterface $hR, $tempLocation)
264
-    {
265
-        if (!file_exists($tempLocation)) {
266
-            return;
267
-        }
268
-
269
-        $hR->setHeader('X-Sabre-Temp', 'true');
270
-        $hR->setStatus(207);
271
-        $hR->setHeader('Content-Type', 'application/xml; charset=utf-8');
272
-
273
-        $properties = [
274
-            'href' => $request->getPath(),
275
-            200 => [
276
-                '{DAV:}getlastmodified' => new Xml\Property\GetLastModified(filemtime($tempLocation)),
277
-                '{DAV:}getcontentlength' => filesize($tempLocation),
278
-                '{DAV:}resourcetype' => new Xml\Property\ResourceType(null),
279
-                '{'.Server::NS_SABREDAV.'}tempFile' => true,
280
-            ],
281
-        ];
282
-
283
-        $data = $this->server->generateMultiStatus([$properties]);
284
-        $hR->setBody($data);
285
-
286
-        return false;
287
-    }
288
-
289
-    /**
290
-     * This method returns the directory where the temporary files should be stored.
291
-     *
292
-     * @return string
293
-     */
294
-    protected function getDataDir()
295
-    {
296
-        return $this->dataDir;
297
-    }
37
+	/**
38
+	 * This is the list of patterns we intercept.
39
+	 * If new patterns are added, they must be valid patterns for preg_match.
40
+	 *
41
+	 * @var array
42
+	 */
43
+	public $temporaryFilePatterns = [
44
+		'/^\._(.*)$/',     // OS/X resource forks
45
+		'/^.DS_Store$/',   // OS/X custom folder settings
46
+		'/^desktop.ini$/', // Windows custom folder settings
47
+		'/^Thumbs.db$/',   // Windows thumbnail cache
48
+		'/^.(.*).swp$/',   // ViM temporary files
49
+		'/^\.dat(.*)$/',   // Smultron seems to create these
50
+		'/^~lock.(.*)#$/', // Windows 7 lockfiles
51
+	];
52
+
53
+	/**
54
+	 * A reference to the main Server class.
55
+	 *
56
+	 * @var \Sabre\DAV\Server
57
+	 */
58
+	protected $server;
59
+
60
+	/**
61
+	 * This is the directory where this plugin
62
+	 * will store it's files.
63
+	 *
64
+	 * @var string
65
+	 */
66
+	private $dataDir;
67
+
68
+	/**
69
+	 * Creates the plugin.
70
+	 *
71
+	 * Make sure you specify a directory for your files. If you don't, we
72
+	 * will use PHP's directory for session-storage instead, and you might
73
+	 * not want that.
74
+	 *
75
+	 * @param string|null $dataDir
76
+	 */
77
+	public function __construct($dataDir = null)
78
+	{
79
+		if (!$dataDir) {
80
+			$dataDir = ini_get('session.save_path').'/sabredav/';
81
+		}
82
+		if (!is_dir($dataDir)) {
83
+			mkdir($dataDir);
84
+		}
85
+		$this->dataDir = $dataDir;
86
+	}
87
+
88
+	/**
89
+	 * Initialize the plugin.
90
+	 *
91
+	 * This is called automatically be the Server class after this plugin is
92
+	 * added with Sabre\DAV\Server::addPlugin()
93
+	 */
94
+	public function initialize(Server $server)
95
+	{
96
+		$this->server = $server;
97
+		$server->on('beforeMethod:*', [$this, 'beforeMethod']);
98
+		$server->on('beforeCreateFile', [$this, 'beforeCreateFile']);
99
+	}
100
+
101
+	/**
102
+	 * This method is called before any HTTP method handler.
103
+	 *
104
+	 * This method intercepts any GET, DELETE, PUT and PROPFIND calls to
105
+	 * filenames that are known to match the 'temporary file' regex.
106
+	 *
107
+	 * @return bool
108
+	 */
109
+	public function beforeMethod(RequestInterface $request, ResponseInterface $response)
110
+	{
111
+		if (!$tempLocation = $this->isTempFile($request->getPath())) {
112
+			return;
113
+		}
114
+
115
+		switch ($request->getMethod()) {
116
+			case 'GET':
117
+				return $this->httpGet($request, $response, $tempLocation);
118
+			case 'PUT':
119
+				return $this->httpPut($request, $response, $tempLocation);
120
+			case 'PROPFIND':
121
+				return $this->httpPropfind($request, $response, $tempLocation);
122
+			case 'DELETE':
123
+				return $this->httpDelete($request, $response, $tempLocation);
124
+		}
125
+
126
+		return;
127
+	}
128
+
129
+	/**
130
+	 * This method is invoked if some subsystem creates a new file.
131
+	 *
132
+	 * This is used to deal with HTTP LOCK requests which create a new
133
+	 * file.
134
+	 *
135
+	 * @param string   $uri
136
+	 * @param resource $data
137
+	 * @param bool     $modified should be set to true, if this event handler
138
+	 *                           changed &$data
139
+	 *
140
+	 * @return bool
141
+	 */
142
+	public function beforeCreateFile($uri, $data, ICollection $parent, $modified)
143
+	{
144
+		if ($tempPath = $this->isTempFile($uri)) {
145
+			$hR = $this->server->httpResponse;
146
+			$hR->setHeader('X-Sabre-Temp', 'true');
147
+			file_put_contents($tempPath, $data);
148
+
149
+			return false;
150
+		}
151
+
152
+		return;
153
+	}
154
+
155
+	/**
156
+	 * This method will check if the url matches the temporary file pattern
157
+	 * if it does, it will return an path based on $this->dataDir for the
158
+	 * temporary file storage.
159
+	 *
160
+	 * @param string $path
161
+	 *
162
+	 * @return bool|string
163
+	 */
164
+	protected function isTempFile($path)
165
+	{
166
+		// We're only interested in the basename.
167
+		list(, $tempPath) = Uri\split($path);
168
+
169
+		if (null === $tempPath) {
170
+			return false;
171
+		}
172
+
173
+		foreach ($this->temporaryFilePatterns as $tempFile) {
174
+			if (preg_match($tempFile, $tempPath)) {
175
+				return $this->getDataDir().'/sabredav_'.md5($path).'.tempfile';
176
+			}
177
+		}
178
+
179
+		return false;
180
+	}
181
+
182
+	/**
183
+	 * This method handles the GET method for temporary files.
184
+	 * If the file doesn't exist, it will return false which will kick in
185
+	 * the regular system for the GET method.
186
+	 *
187
+	 * @param string $tempLocation
188
+	 *
189
+	 * @return bool
190
+	 */
191
+	public function httpGet(RequestInterface $request, ResponseInterface $hR, $tempLocation)
192
+	{
193
+		if (!file_exists($tempLocation)) {
194
+			return;
195
+		}
196
+
197
+		$hR->setHeader('Content-Type', 'application/octet-stream');
198
+		$hR->setHeader('Content-Length', filesize($tempLocation));
199
+		$hR->setHeader('X-Sabre-Temp', 'true');
200
+		$hR->setStatus(200);
201
+		$hR->setBody(fopen($tempLocation, 'r'));
202
+
203
+		return false;
204
+	}
205
+
206
+	/**
207
+	 * This method handles the PUT method.
208
+	 *
209
+	 * @param string $tempLocation
210
+	 *
211
+	 * @return bool
212
+	 */
213
+	public function httpPut(RequestInterface $request, ResponseInterface $hR, $tempLocation)
214
+	{
215
+		$hR->setHeader('X-Sabre-Temp', 'true');
216
+
217
+		$newFile = !file_exists($tempLocation);
218
+
219
+		if (!$newFile && ($this->server->httpRequest->getHeader('If-None-Match'))) {
220
+			throw new Exception\PreconditionFailed('The resource already exists, and an If-None-Match header was supplied');
221
+		}
222
+
223
+		file_put_contents($tempLocation, $this->server->httpRequest->getBody());
224
+		$hR->setStatus($newFile ? 201 : 200);
225
+
226
+		return false;
227
+	}
228
+
229
+	/**
230
+	 * This method handles the DELETE method.
231
+	 *
232
+	 * If the file didn't exist, it will return false, which will make the
233
+	 * standard HTTP DELETE handler kick in.
234
+	 *
235
+	 * @param string $tempLocation
236
+	 *
237
+	 * @return bool
238
+	 */
239
+	public function httpDelete(RequestInterface $request, ResponseInterface $hR, $tempLocation)
240
+	{
241
+		if (!file_exists($tempLocation)) {
242
+			return;
243
+		}
244
+
245
+		unlink($tempLocation);
246
+		$hR->setHeader('X-Sabre-Temp', 'true');
247
+		$hR->setStatus(204);
248
+
249
+		return false;
250
+	}
251
+
252
+	/**
253
+	 * This method handles the PROPFIND method.
254
+	 *
255
+	 * It's a very lazy method, it won't bother checking the request body
256
+	 * for which properties were requested, and just sends back a default
257
+	 * set of properties.
258
+	 *
259
+	 * @param string $tempLocation
260
+	 *
261
+	 * @return bool
262
+	 */
263
+	public function httpPropfind(RequestInterface $request, ResponseInterface $hR, $tempLocation)
264
+	{
265
+		if (!file_exists($tempLocation)) {
266
+			return;
267
+		}
268
+
269
+		$hR->setHeader('X-Sabre-Temp', 'true');
270
+		$hR->setStatus(207);
271
+		$hR->setHeader('Content-Type', 'application/xml; charset=utf-8');
272
+
273
+		$properties = [
274
+			'href' => $request->getPath(),
275
+			200 => [
276
+				'{DAV:}getlastmodified' => new Xml\Property\GetLastModified(filemtime($tempLocation)),
277
+				'{DAV:}getcontentlength' => filesize($tempLocation),
278
+				'{DAV:}resourcetype' => new Xml\Property\ResourceType(null),
279
+				'{'.Server::NS_SABREDAV.'}tempFile' => true,
280
+			],
281
+		];
282
+
283
+		$data = $this->server->generateMultiStatus([$properties]);
284
+		$hR->setBody($data);
285
+
286
+		return false;
287
+	}
288
+
289
+	/**
290
+	 * This method returns the directory where the temporary files should be stored.
291
+	 *
292
+	 * @return string
293
+	 */
294
+	protected function getDataDir()
295
+	{
296
+		return $this->dataDir;
297
+	}
298 298
 }
Please login to merge, or discard this patch.
Spacing   +5 added lines, -5 removed lines patch added patch discarded remove patch
@@ -41,12 +41,12 @@
 block discarded – undo
41 41
      * @var array
42 42
      */
43 43
     public $temporaryFilePatterns = [
44
-        '/^\._(.*)$/',     // OS/X resource forks
45
-        '/^.DS_Store$/',   // OS/X custom folder settings
44
+        '/^\._(.*)$/', // OS/X resource forks
45
+        '/^.DS_Store$/', // OS/X custom folder settings
46 46
         '/^desktop.ini$/', // Windows custom folder settings
47
-        '/^Thumbs.db$/',   // Windows thumbnail cache
48
-        '/^.(.*).swp$/',   // ViM temporary files
49
-        '/^\.dat(.*)$/',   // Smultron seems to create these
47
+        '/^Thumbs.db$/', // Windows thumbnail cache
48
+        '/^.(.*).swp$/', // ViM temporary files
49
+        '/^\.dat(.*)$/', // Smultron seems to create these
50 50
         '/^~lock.(.*)#$/', // Windows 7 lockfiles
51 51
     ];
52 52
 
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/DAV/Locks/Plugin.php 2 patches
Indentation   +523 added lines, -523 removed lines patch added patch discarded remove patch
@@ -24,527 +24,527 @@
 block discarded – undo
24 24
  */
25 25
 class Plugin extends DAV\ServerPlugin
26 26
 {
27
-    /**
28
-     * locksBackend.
29
-     *
30
-     * @var Backend\BackendInterface
31
-     */
32
-    protected $locksBackend;
33
-
34
-    /**
35
-     * server.
36
-     *
37
-     * @var DAV\Server
38
-     */
39
-    protected $server;
40
-
41
-    /**
42
-     * __construct.
43
-     */
44
-    public function __construct(Backend\BackendInterface $locksBackend)
45
-    {
46
-        $this->locksBackend = $locksBackend;
47
-    }
48
-
49
-    /**
50
-     * Initializes the plugin.
51
-     *
52
-     * This method is automatically called by the Server class after addPlugin.
53
-     */
54
-    public function initialize(DAV\Server $server)
55
-    {
56
-        $this->server = $server;
57
-
58
-        $this->server->xml->elementMap['{DAV:}lockinfo'] = 'Sabre\\DAV\\Xml\\Request\\Lock';
59
-
60
-        $server->on('method:LOCK', [$this, 'httpLock']);
61
-        $server->on('method:UNLOCK', [$this, 'httpUnlock']);
62
-        $server->on('validateTokens', [$this, 'validateTokens']);
63
-        $server->on('propFind', [$this, 'propFind']);
64
-        $server->on('afterUnbind', [$this, 'afterUnbind']);
65
-    }
66
-
67
-    /**
68
-     * Returns a plugin name.
69
-     *
70
-     * Using this name other plugins will be able to access other plugins
71
-     * using Sabre\DAV\Server::getPlugin
72
-     *
73
-     * @return string
74
-     */
75
-    public function getPluginName()
76
-    {
77
-        return 'locks';
78
-    }
79
-
80
-    /**
81
-     * This method is called after most properties have been found
82
-     * it allows us to add in any Lock-related properties.
83
-     */
84
-    public function propFind(DAV\PropFind $propFind, DAV\INode $node)
85
-    {
86
-        $propFind->handle('{DAV:}supportedlock', function () {
87
-            return new DAV\Xml\Property\SupportedLock();
88
-        });
89
-        $propFind->handle('{DAV:}lockdiscovery', function () use ($propFind) {
90
-            return new DAV\Xml\Property\LockDiscovery(
91
-                $this->getLocks($propFind->getPath())
92
-            );
93
-        });
94
-    }
95
-
96
-    /**
97
-     * Use this method to tell the server this plugin defines additional
98
-     * HTTP methods.
99
-     *
100
-     * This method is passed a uri. It should only return HTTP methods that are
101
-     * available for the specified uri.
102
-     *
103
-     * @param string $uri
104
-     *
105
-     * @return array
106
-     */
107
-    public function getHTTPMethods($uri)
108
-    {
109
-        return ['LOCK', 'UNLOCK'];
110
-    }
111
-
112
-    /**
113
-     * Returns a list of features for the HTTP OPTIONS Dav: header.
114
-     *
115
-     * In this case this is only the number 2. The 2 in the Dav: header
116
-     * indicates the server supports locks.
117
-     *
118
-     * @return array
119
-     */
120
-    public function getFeatures()
121
-    {
122
-        return [2];
123
-    }
124
-
125
-    /**
126
-     * Returns all lock information on a particular uri.
127
-     *
128
-     * This function should return an array with Sabre\DAV\Locks\LockInfo objects. If there are no locks on a file, return an empty array.
129
-     *
130
-     * Additionally there is also the possibility of locks on parent nodes, so we'll need to traverse every part of the tree
131
-     * If the $returnChildLocks argument is set to true, we'll also traverse all the children of the object
132
-     * for any possible locks and return those as well.
133
-     *
134
-     * @param string $uri
135
-     * @param bool   $returnChildLocks
136
-     *
137
-     * @return array
138
-     */
139
-    public function getLocks($uri, $returnChildLocks = false)
140
-    {
141
-        return $this->locksBackend->getLocks($uri, $returnChildLocks);
142
-    }
143
-
144
-    /**
145
-     * Locks an uri.
146
-     *
147
-     * The WebDAV lock request can be operated to either create a new lock on a file, or to refresh an existing lock
148
-     * If a new lock is created, a full XML body should be supplied, containing information about the lock such as the type
149
-     * of lock (shared or exclusive) and the owner of the lock
150
-     *
151
-     * If a lock is to be refreshed, no body should be supplied and there should be a valid If header containing the lock
152
-     *
153
-     * 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
154
-     *
155
-     * @return bool
156
-     */
157
-    public function httpLock(RequestInterface $request, ResponseInterface $response)
158
-    {
159
-        $uri = $request->getPath();
160
-
161
-        $existingLocks = $this->getLocks($uri);
162
-
163
-        if ($body = $request->getBodyAsString()) {
164
-            // This is a new lock request
165
-
166
-            $existingLock = null;
167
-            // Checking if there's already non-shared locks on the uri.
168
-            foreach ($existingLocks as $existingLock) {
169
-                if (LockInfo::EXCLUSIVE === $existingLock->scope) {
170
-                    throw new DAV\Exception\ConflictingLock($existingLock);
171
-                }
172
-            }
173
-
174
-            $lockInfo = $this->parseLockRequest($body);
175
-            $lockInfo->depth = $this->server->getHTTPDepth();
176
-            $lockInfo->uri = $uri;
177
-            if ($existingLock && LockInfo::SHARED != $lockInfo->scope) {
178
-                throw new DAV\Exception\ConflictingLock($existingLock);
179
-            }
180
-        } else {
181
-            // Gonna check if this was a lock refresh.
182
-            $existingLocks = $this->getLocks($uri);
183
-            $conditions = $this->server->getIfConditions($request);
184
-            $found = null;
185
-
186
-            foreach ($existingLocks as $existingLock) {
187
-                foreach ($conditions as $condition) {
188
-                    foreach ($condition['tokens'] as $token) {
189
-                        if ($token['token'] === 'opaquelocktoken:'.$existingLock->token) {
190
-                            $found = $existingLock;
191
-                            break 3;
192
-                        }
193
-                    }
194
-                }
195
-            }
196
-
197
-            // If none were found, this request is in error.
198
-            if (is_null($found)) {
199
-                if ($existingLocks) {
200
-                    throw new DAV\Exception\Locked(reset($existingLocks));
201
-                } else {
202
-                    throw new DAV\Exception\BadRequest('An xml body is required for lock requests');
203
-                }
204
-            }
205
-
206
-            // This must have been a lock refresh
207
-            $lockInfo = $found;
208
-
209
-            // The resource could have been locked through another uri.
210
-            if ($uri != $lockInfo->uri) {
211
-                $uri = $lockInfo->uri;
212
-            }
213
-        }
214
-
215
-        if ($timeout = $this->getTimeoutHeader()) {
216
-            $lockInfo->timeout = $timeout;
217
-        }
218
-
219
-        $newFile = false;
220
-
221
-        // 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
222
-        try {
223
-            $this->server->tree->getNodeForPath($uri);
224
-
225
-            // We need to call the beforeWriteContent event for RFC3744
226
-            // Edit: looks like this is not used, and causing problems now.
227
-            //
228
-            // See Issue 222
229
-            // $this->server->emit('beforeWriteContent',array($uri));
230
-        } catch (DAV\Exception\NotFound $e) {
231
-            // It didn't, lets create it
232
-            $this->server->createFile($uri, fopen('php://memory', 'r'));
233
-            $newFile = true;
234
-        }
235
-
236
-        $this->lockNode($uri, $lockInfo);
237
-
238
-        $response->setHeader('Content-Type', 'application/xml; charset=utf-8');
239
-        $response->setHeader('Lock-Token', '<opaquelocktoken:'.$lockInfo->token.'>');
240
-        $response->setStatus($newFile ? 201 : 200);
241
-        $response->setBody($this->generateLockResponse($lockInfo));
242
-
243
-        // Returning false will interrupt the event chain and mark this method
244
-        // as 'handled'.
245
-        return false;
246
-    }
247
-
248
-    /**
249
-     * Unlocks a uri.
250
-     *
251
-     * 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
252
-     * The server should return 204 (No content) on success
253
-     */
254
-    public function httpUnlock(RequestInterface $request, ResponseInterface $response)
255
-    {
256
-        $lockToken = $request->getHeader('Lock-Token');
257
-
258
-        // If the locktoken header is not supplied, we need to throw a bad request exception
259
-        if (!$lockToken) {
260
-            throw new DAV\Exception\BadRequest('No lock token was supplied');
261
-        }
262
-        $path = $request->getPath();
263
-        $locks = $this->getLocks($path);
264
-
265
-        // Windows sometimes forgets to include < and > in the Lock-Token
266
-        // header
267
-        if ('<' !== $lockToken[0]) {
268
-            $lockToken = '<'.$lockToken.'>';
269
-        }
270
-
271
-        foreach ($locks as $lock) {
272
-            if ('<opaquelocktoken:'.$lock->token.'>' == $lockToken) {
273
-                $this->unlockNode($path, $lock);
274
-                $response->setHeader('Content-Length', '0');
275
-                $response->setStatus(204);
276
-
277
-                // Returning false will break the method chain, and mark the
278
-                // method as 'handled'.
279
-                return false;
280
-            }
281
-        }
282
-
283
-        // If we got here, it means the locktoken was invalid
284
-        throw new DAV\Exception\LockTokenMatchesRequestUri();
285
-    }
286
-
287
-    /**
288
-     * This method is called after a node is deleted.
289
-     *
290
-     * We use this event to clean up any locks that still exist on the node.
291
-     *
292
-     * @param string $path
293
-     */
294
-    public function afterUnbind($path)
295
-    {
296
-        $locks = $this->getLocks($path, $includeChildren = true);
297
-        foreach ($locks as $lock) {
298
-            // don't delete a lock on a parent dir
299
-            if (0 !== strpos($lock->uri, $path)) {
300
-                continue;
301
-            }
302
-            $this->unlockNode($path, $lock);
303
-        }
304
-    }
305
-
306
-    /**
307
-     * Locks a uri.
308
-     *
309
-     * All the locking information is supplied in the lockInfo object. The object has a suggested timeout, but this can be safely ignored
310
-     * It is important that if the existing timeout is ignored, the property is overwritten, as this needs to be sent back to the client
311
-     *
312
-     * @param string $uri
313
-     *
314
-     * @return bool
315
-     */
316
-    public function lockNode($uri, LockInfo $lockInfo)
317
-    {
318
-        if (!$this->server->emit('beforeLock', [$uri, $lockInfo])) {
319
-            return;
320
-        }
321
-
322
-        return $this->locksBackend->lock($uri, $lockInfo);
323
-    }
324
-
325
-    /**
326
-     * Unlocks a uri.
327
-     *
328
-     * This method removes a lock from a uri. It is assumed all the supplied information is correct and verified
329
-     *
330
-     * @param string $uri
331
-     *
332
-     * @return bool
333
-     */
334
-    public function unlockNode($uri, LockInfo $lockInfo)
335
-    {
336
-        if (!$this->server->emit('beforeUnlock', [$uri, $lockInfo])) {
337
-            return;
338
-        }
339
-
340
-        return $this->locksBackend->unlock($uri, $lockInfo);
341
-    }
342
-
343
-    /**
344
-     * Returns the contents of the HTTP Timeout header.
345
-     *
346
-     * The method formats the header into an integer.
347
-     *
348
-     * @return int
349
-     */
350
-    public function getTimeoutHeader()
351
-    {
352
-        $header = $this->server->httpRequest->getHeader('Timeout');
353
-
354
-        if ($header) {
355
-            if (0 === stripos($header, 'second-')) {
356
-                $header = (int) (substr($header, 7));
357
-            } elseif (0 === stripos($header, 'infinite')) {
358
-                $header = LockInfo::TIMEOUT_INFINITE;
359
-            } else {
360
-                throw new DAV\Exception\BadRequest('Invalid HTTP timeout header');
361
-            }
362
-        } else {
363
-            $header = 0;
364
-        }
365
-
366
-        return $header;
367
-    }
368
-
369
-    /**
370
-     * Generates the response for successful LOCK requests.
371
-     *
372
-     * @return string
373
-     */
374
-    protected function generateLockResponse(LockInfo $lockInfo)
375
-    {
376
-        return $this->server->xml->write('{DAV:}prop', [
377
-            '{DAV:}lockdiscovery' => new DAV\Xml\Property\LockDiscovery([$lockInfo]),
378
-        ], $this->server->getBaseUri());
379
-    }
380
-
381
-    /**
382
-     * The validateTokens event is triggered before every request.
383
-     *
384
-     * It's a moment where this plugin can check all the supplied lock tokens
385
-     * in the If: header, and check if they are valid.
386
-     *
387
-     * In addition, it will also ensure that it checks any missing lokens that
388
-     * must be present in the request, and reject requests without the proper
389
-     * tokens.
390
-     *
391
-     * @param mixed $conditions
392
-     */
393
-    public function validateTokens(RequestInterface $request, &$conditions)
394
-    {
395
-        // First we need to gather a list of locks that must be satisfied.
396
-        $mustLocks = [];
397
-        $method = $request->getMethod();
398
-
399
-        // Methods not in that list are operations that doesn't alter any
400
-        // resources, and we don't need to check the lock-states for.
401
-        switch ($method) {
402
-            case 'DELETE':
403
-                $mustLocks = array_merge($mustLocks, $this->getLocks(
404
-                    $request->getPath(),
405
-                    true
406
-                ));
407
-                break;
408
-            case 'MKCOL':
409
-            case 'MKCALENDAR':
410
-            case 'PROPPATCH':
411
-            case 'PUT':
412
-            case 'PATCH':
413
-                $mustLocks = array_merge($mustLocks, $this->getLocks(
414
-                    $request->getPath(),
415
-                    false
416
-                ));
417
-                break;
418
-            case 'MOVE':
419
-                $mustLocks = array_merge($mustLocks, $this->getLocks(
420
-                    $request->getPath(),
421
-                    true
422
-                ));
423
-                $mustLocks = array_merge($mustLocks, $this->getLocks(
424
-                    $this->server->calculateUri($request->getHeader('Destination')),
425
-                    false
426
-                ));
427
-                break;
428
-            case 'COPY':
429
-                $mustLocks = array_merge($mustLocks, $this->getLocks(
430
-                    $this->server->calculateUri($request->getHeader('Destination')),
431
-                    false
432
-                ));
433
-                break;
434
-            case 'LOCK':
435
-                //Temporary measure.. figure out later why this is needed
436
-                // Here we basically ignore all incoming tokens...
437
-                foreach ($conditions as $ii => $condition) {
438
-                    foreach ($condition['tokens'] as $jj => $token) {
439
-                        $conditions[$ii]['tokens'][$jj]['validToken'] = true;
440
-                    }
441
-                }
442
-
443
-                return;
444
-        }
445
-
446
-        // It's possible that there's identical locks, because of shared
447
-        // parents. We're removing the duplicates here.
448
-        $tmp = [];
449
-        foreach ($mustLocks as $lock) {
450
-            $tmp[$lock->token] = $lock;
451
-        }
452
-        $mustLocks = array_values($tmp);
453
-
454
-        foreach ($conditions as $kk => $condition) {
455
-            foreach ($condition['tokens'] as $ii => $token) {
456
-                // Lock tokens always start with opaquelocktoken:
457
-                if ('opaquelocktoken:' !== substr($token['token'], 0, 16)) {
458
-                    continue;
459
-                }
460
-
461
-                $checkToken = substr($token['token'], 16);
462
-                // Looping through our list with locks.
463
-                foreach ($mustLocks as $jj => $mustLock) {
464
-                    if ($mustLock->token == $checkToken) {
465
-                        // We have a match!
466
-                        // Removing this one from mustlocks
467
-                        unset($mustLocks[$jj]);
468
-
469
-                        // Marking the condition as valid.
470
-                        $conditions[$kk]['tokens'][$ii]['validToken'] = true;
471
-
472
-                        // Advancing to the next token
473
-                        continue 2;
474
-                    }
475
-                }
476
-
477
-                // If we got here, it means that there was a
478
-                // lock-token, but it was not in 'mustLocks'.
479
-                //
480
-                // This is an edge-case, as it could mean that token
481
-                // was specified with a url that was not 'required' to
482
-                // check. So we're doing one extra lookup to make sure
483
-                // we really don't know this token.
484
-                //
485
-                // This also gets triggered when the user specified a
486
-                // lock-token that was expired.
487
-                $oddLocks = $this->getLocks($condition['uri']);
488
-                foreach ($oddLocks as $oddLock) {
489
-                    if ($oddLock->token === $checkToken) {
490
-                        // We have a hit!
491
-                        $conditions[$kk]['tokens'][$ii]['validToken'] = true;
492
-                        continue 2;
493
-                    }
494
-                }
495
-
496
-                // If we get all the way here, the lock-token was
497
-                // really unknown.
498
-            }
499
-        }
500
-
501
-        // If there's any locks left in the 'mustLocks' array, it means that
502
-        // the resource was locked and we must block it.
503
-        if ($mustLocks) {
504
-            throw new DAV\Exception\Locked(reset($mustLocks));
505
-        }
506
-    }
507
-
508
-    /**
509
-     * Parses a webdav lock xml body, and returns a new Sabre\DAV\Locks\LockInfo object.
510
-     *
511
-     * @param string $body
512
-     *
513
-     * @return LockInfo
514
-     */
515
-    protected function parseLockRequest($body)
516
-    {
517
-        $result = $this->server->xml->expect(
518
-            '{DAV:}lockinfo',
519
-            $body
520
-        );
521
-
522
-        $lockInfo = new LockInfo();
523
-
524
-        $lockInfo->owner = $result->owner;
525
-        $lockInfo->token = DAV\UUIDUtil::getUUID();
526
-        $lockInfo->scope = $result->scope;
527
-
528
-        return $lockInfo;
529
-    }
530
-
531
-    /**
532
-     * Returns a bunch of meta-data about the plugin.
533
-     *
534
-     * Providing this information is optional, and is mainly displayed by the
535
-     * Browser plugin.
536
-     *
537
-     * The description key in the returned array may contain html and will not
538
-     * be sanitized.
539
-     *
540
-     * @return array
541
-     */
542
-    public function getPluginInfo()
543
-    {
544
-        return [
545
-            'name' => $this->getPluginName(),
546
-            'description' => 'The locks plugin turns this server into a class-2 WebDAV server and adds support for LOCK and UNLOCK',
547
-            'link' => 'http://sabre.io/dav/locks/',
548
-        ];
549
-    }
27
+	/**
28
+	 * locksBackend.
29
+	 *
30
+	 * @var Backend\BackendInterface
31
+	 */
32
+	protected $locksBackend;
33
+
34
+	/**
35
+	 * server.
36
+	 *
37
+	 * @var DAV\Server
38
+	 */
39
+	protected $server;
40
+
41
+	/**
42
+	 * __construct.
43
+	 */
44
+	public function __construct(Backend\BackendInterface $locksBackend)
45
+	{
46
+		$this->locksBackend = $locksBackend;
47
+	}
48
+
49
+	/**
50
+	 * Initializes the plugin.
51
+	 *
52
+	 * This method is automatically called by the Server class after addPlugin.
53
+	 */
54
+	public function initialize(DAV\Server $server)
55
+	{
56
+		$this->server = $server;
57
+
58
+		$this->server->xml->elementMap['{DAV:}lockinfo'] = 'Sabre\\DAV\\Xml\\Request\\Lock';
59
+
60
+		$server->on('method:LOCK', [$this, 'httpLock']);
61
+		$server->on('method:UNLOCK', [$this, 'httpUnlock']);
62
+		$server->on('validateTokens', [$this, 'validateTokens']);
63
+		$server->on('propFind', [$this, 'propFind']);
64
+		$server->on('afterUnbind', [$this, 'afterUnbind']);
65
+	}
66
+
67
+	/**
68
+	 * Returns a plugin name.
69
+	 *
70
+	 * Using this name other plugins will be able to access other plugins
71
+	 * using Sabre\DAV\Server::getPlugin
72
+	 *
73
+	 * @return string
74
+	 */
75
+	public function getPluginName()
76
+	{
77
+		return 'locks';
78
+	}
79
+
80
+	/**
81
+	 * This method is called after most properties have been found
82
+	 * it allows us to add in any Lock-related properties.
83
+	 */
84
+	public function propFind(DAV\PropFind $propFind, DAV\INode $node)
85
+	{
86
+		$propFind->handle('{DAV:}supportedlock', function () {
87
+			return new DAV\Xml\Property\SupportedLock();
88
+		});
89
+		$propFind->handle('{DAV:}lockdiscovery', function () use ($propFind) {
90
+			return new DAV\Xml\Property\LockDiscovery(
91
+				$this->getLocks($propFind->getPath())
92
+			);
93
+		});
94
+	}
95
+
96
+	/**
97
+	 * Use this method to tell the server this plugin defines additional
98
+	 * HTTP methods.
99
+	 *
100
+	 * This method is passed a uri. It should only return HTTP methods that are
101
+	 * available for the specified uri.
102
+	 *
103
+	 * @param string $uri
104
+	 *
105
+	 * @return array
106
+	 */
107
+	public function getHTTPMethods($uri)
108
+	{
109
+		return ['LOCK', 'UNLOCK'];
110
+	}
111
+
112
+	/**
113
+	 * Returns a list of features for the HTTP OPTIONS Dav: header.
114
+	 *
115
+	 * In this case this is only the number 2. The 2 in the Dav: header
116
+	 * indicates the server supports locks.
117
+	 *
118
+	 * @return array
119
+	 */
120
+	public function getFeatures()
121
+	{
122
+		return [2];
123
+	}
124
+
125
+	/**
126
+	 * Returns all lock information on a particular uri.
127
+	 *
128
+	 * This function should return an array with Sabre\DAV\Locks\LockInfo objects. If there are no locks on a file, return an empty array.
129
+	 *
130
+	 * Additionally there is also the possibility of locks on parent nodes, so we'll need to traverse every part of the tree
131
+	 * If the $returnChildLocks argument is set to true, we'll also traverse all the children of the object
132
+	 * for any possible locks and return those as well.
133
+	 *
134
+	 * @param string $uri
135
+	 * @param bool   $returnChildLocks
136
+	 *
137
+	 * @return array
138
+	 */
139
+	public function getLocks($uri, $returnChildLocks = false)
140
+	{
141
+		return $this->locksBackend->getLocks($uri, $returnChildLocks);
142
+	}
143
+
144
+	/**
145
+	 * Locks an uri.
146
+	 *
147
+	 * The WebDAV lock request can be operated to either create a new lock on a file, or to refresh an existing lock
148
+	 * If a new lock is created, a full XML body should be supplied, containing information about the lock such as the type
149
+	 * of lock (shared or exclusive) and the owner of the lock
150
+	 *
151
+	 * If a lock is to be refreshed, no body should be supplied and there should be a valid If header containing the lock
152
+	 *
153
+	 * 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
154
+	 *
155
+	 * @return bool
156
+	 */
157
+	public function httpLock(RequestInterface $request, ResponseInterface $response)
158
+	{
159
+		$uri = $request->getPath();
160
+
161
+		$existingLocks = $this->getLocks($uri);
162
+
163
+		if ($body = $request->getBodyAsString()) {
164
+			// This is a new lock request
165
+
166
+			$existingLock = null;
167
+			// Checking if there's already non-shared locks on the uri.
168
+			foreach ($existingLocks as $existingLock) {
169
+				if (LockInfo::EXCLUSIVE === $existingLock->scope) {
170
+					throw new DAV\Exception\ConflictingLock($existingLock);
171
+				}
172
+			}
173
+
174
+			$lockInfo = $this->parseLockRequest($body);
175
+			$lockInfo->depth = $this->server->getHTTPDepth();
176
+			$lockInfo->uri = $uri;
177
+			if ($existingLock && LockInfo::SHARED != $lockInfo->scope) {
178
+				throw new DAV\Exception\ConflictingLock($existingLock);
179
+			}
180
+		} else {
181
+			// Gonna check if this was a lock refresh.
182
+			$existingLocks = $this->getLocks($uri);
183
+			$conditions = $this->server->getIfConditions($request);
184
+			$found = null;
185
+
186
+			foreach ($existingLocks as $existingLock) {
187
+				foreach ($conditions as $condition) {
188
+					foreach ($condition['tokens'] as $token) {
189
+						if ($token['token'] === 'opaquelocktoken:'.$existingLock->token) {
190
+							$found = $existingLock;
191
+							break 3;
192
+						}
193
+					}
194
+				}
195
+			}
196
+
197
+			// If none were found, this request is in error.
198
+			if (is_null($found)) {
199
+				if ($existingLocks) {
200
+					throw new DAV\Exception\Locked(reset($existingLocks));
201
+				} else {
202
+					throw new DAV\Exception\BadRequest('An xml body is required for lock requests');
203
+				}
204
+			}
205
+
206
+			// This must have been a lock refresh
207
+			$lockInfo = $found;
208
+
209
+			// The resource could have been locked through another uri.
210
+			if ($uri != $lockInfo->uri) {
211
+				$uri = $lockInfo->uri;
212
+			}
213
+		}
214
+
215
+		if ($timeout = $this->getTimeoutHeader()) {
216
+			$lockInfo->timeout = $timeout;
217
+		}
218
+
219
+		$newFile = false;
220
+
221
+		// 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
222
+		try {
223
+			$this->server->tree->getNodeForPath($uri);
224
+
225
+			// We need to call the beforeWriteContent event for RFC3744
226
+			// Edit: looks like this is not used, and causing problems now.
227
+			//
228
+			// See Issue 222
229
+			// $this->server->emit('beforeWriteContent',array($uri));
230
+		} catch (DAV\Exception\NotFound $e) {
231
+			// It didn't, lets create it
232
+			$this->server->createFile($uri, fopen('php://memory', 'r'));
233
+			$newFile = true;
234
+		}
235
+
236
+		$this->lockNode($uri, $lockInfo);
237
+
238
+		$response->setHeader('Content-Type', 'application/xml; charset=utf-8');
239
+		$response->setHeader('Lock-Token', '<opaquelocktoken:'.$lockInfo->token.'>');
240
+		$response->setStatus($newFile ? 201 : 200);
241
+		$response->setBody($this->generateLockResponse($lockInfo));
242
+
243
+		// Returning false will interrupt the event chain and mark this method
244
+		// as 'handled'.
245
+		return false;
246
+	}
247
+
248
+	/**
249
+	 * Unlocks a uri.
250
+	 *
251
+	 * 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
252
+	 * The server should return 204 (No content) on success
253
+	 */
254
+	public function httpUnlock(RequestInterface $request, ResponseInterface $response)
255
+	{
256
+		$lockToken = $request->getHeader('Lock-Token');
257
+
258
+		// If the locktoken header is not supplied, we need to throw a bad request exception
259
+		if (!$lockToken) {
260
+			throw new DAV\Exception\BadRequest('No lock token was supplied');
261
+		}
262
+		$path = $request->getPath();
263
+		$locks = $this->getLocks($path);
264
+
265
+		// Windows sometimes forgets to include < and > in the Lock-Token
266
+		// header
267
+		if ('<' !== $lockToken[0]) {
268
+			$lockToken = '<'.$lockToken.'>';
269
+		}
270
+
271
+		foreach ($locks as $lock) {
272
+			if ('<opaquelocktoken:'.$lock->token.'>' == $lockToken) {
273
+				$this->unlockNode($path, $lock);
274
+				$response->setHeader('Content-Length', '0');
275
+				$response->setStatus(204);
276
+
277
+				// Returning false will break the method chain, and mark the
278
+				// method as 'handled'.
279
+				return false;
280
+			}
281
+		}
282
+
283
+		// If we got here, it means the locktoken was invalid
284
+		throw new DAV\Exception\LockTokenMatchesRequestUri();
285
+	}
286
+
287
+	/**
288
+	 * This method is called after a node is deleted.
289
+	 *
290
+	 * We use this event to clean up any locks that still exist on the node.
291
+	 *
292
+	 * @param string $path
293
+	 */
294
+	public function afterUnbind($path)
295
+	{
296
+		$locks = $this->getLocks($path, $includeChildren = true);
297
+		foreach ($locks as $lock) {
298
+			// don't delete a lock on a parent dir
299
+			if (0 !== strpos($lock->uri, $path)) {
300
+				continue;
301
+			}
302
+			$this->unlockNode($path, $lock);
303
+		}
304
+	}
305
+
306
+	/**
307
+	 * Locks a uri.
308
+	 *
309
+	 * All the locking information is supplied in the lockInfo object. The object has a suggested timeout, but this can be safely ignored
310
+	 * It is important that if the existing timeout is ignored, the property is overwritten, as this needs to be sent back to the client
311
+	 *
312
+	 * @param string $uri
313
+	 *
314
+	 * @return bool
315
+	 */
316
+	public function lockNode($uri, LockInfo $lockInfo)
317
+	{
318
+		if (!$this->server->emit('beforeLock', [$uri, $lockInfo])) {
319
+			return;
320
+		}
321
+
322
+		return $this->locksBackend->lock($uri, $lockInfo);
323
+	}
324
+
325
+	/**
326
+	 * Unlocks a uri.
327
+	 *
328
+	 * This method removes a lock from a uri. It is assumed all the supplied information is correct and verified
329
+	 *
330
+	 * @param string $uri
331
+	 *
332
+	 * @return bool
333
+	 */
334
+	public function unlockNode($uri, LockInfo $lockInfo)
335
+	{
336
+		if (!$this->server->emit('beforeUnlock', [$uri, $lockInfo])) {
337
+			return;
338
+		}
339
+
340
+		return $this->locksBackend->unlock($uri, $lockInfo);
341
+	}
342
+
343
+	/**
344
+	 * Returns the contents of the HTTP Timeout header.
345
+	 *
346
+	 * The method formats the header into an integer.
347
+	 *
348
+	 * @return int
349
+	 */
350
+	public function getTimeoutHeader()
351
+	{
352
+		$header = $this->server->httpRequest->getHeader('Timeout');
353
+
354
+		if ($header) {
355
+			if (0 === stripos($header, 'second-')) {
356
+				$header = (int) (substr($header, 7));
357
+			} elseif (0 === stripos($header, 'infinite')) {
358
+				$header = LockInfo::TIMEOUT_INFINITE;
359
+			} else {
360
+				throw new DAV\Exception\BadRequest('Invalid HTTP timeout header');
361
+			}
362
+		} else {
363
+			$header = 0;
364
+		}
365
+
366
+		return $header;
367
+	}
368
+
369
+	/**
370
+	 * Generates the response for successful LOCK requests.
371
+	 *
372
+	 * @return string
373
+	 */
374
+	protected function generateLockResponse(LockInfo $lockInfo)
375
+	{
376
+		return $this->server->xml->write('{DAV:}prop', [
377
+			'{DAV:}lockdiscovery' => new DAV\Xml\Property\LockDiscovery([$lockInfo]),
378
+		], $this->server->getBaseUri());
379
+	}
380
+
381
+	/**
382
+	 * The validateTokens event is triggered before every request.
383
+	 *
384
+	 * It's a moment where this plugin can check all the supplied lock tokens
385
+	 * in the If: header, and check if they are valid.
386
+	 *
387
+	 * In addition, it will also ensure that it checks any missing lokens that
388
+	 * must be present in the request, and reject requests without the proper
389
+	 * tokens.
390
+	 *
391
+	 * @param mixed $conditions
392
+	 */
393
+	public function validateTokens(RequestInterface $request, &$conditions)
394
+	{
395
+		// First we need to gather a list of locks that must be satisfied.
396
+		$mustLocks = [];
397
+		$method = $request->getMethod();
398
+
399
+		// Methods not in that list are operations that doesn't alter any
400
+		// resources, and we don't need to check the lock-states for.
401
+		switch ($method) {
402
+			case 'DELETE':
403
+				$mustLocks = array_merge($mustLocks, $this->getLocks(
404
+					$request->getPath(),
405
+					true
406
+				));
407
+				break;
408
+			case 'MKCOL':
409
+			case 'MKCALENDAR':
410
+			case 'PROPPATCH':
411
+			case 'PUT':
412
+			case 'PATCH':
413
+				$mustLocks = array_merge($mustLocks, $this->getLocks(
414
+					$request->getPath(),
415
+					false
416
+				));
417
+				break;
418
+			case 'MOVE':
419
+				$mustLocks = array_merge($mustLocks, $this->getLocks(
420
+					$request->getPath(),
421
+					true
422
+				));
423
+				$mustLocks = array_merge($mustLocks, $this->getLocks(
424
+					$this->server->calculateUri($request->getHeader('Destination')),
425
+					false
426
+				));
427
+				break;
428
+			case 'COPY':
429
+				$mustLocks = array_merge($mustLocks, $this->getLocks(
430
+					$this->server->calculateUri($request->getHeader('Destination')),
431
+					false
432
+				));
433
+				break;
434
+			case 'LOCK':
435
+				//Temporary measure.. figure out later why this is needed
436
+				// Here we basically ignore all incoming tokens...
437
+				foreach ($conditions as $ii => $condition) {
438
+					foreach ($condition['tokens'] as $jj => $token) {
439
+						$conditions[$ii]['tokens'][$jj]['validToken'] = true;
440
+					}
441
+				}
442
+
443
+				return;
444
+		}
445
+
446
+		// It's possible that there's identical locks, because of shared
447
+		// parents. We're removing the duplicates here.
448
+		$tmp = [];
449
+		foreach ($mustLocks as $lock) {
450
+			$tmp[$lock->token] = $lock;
451
+		}
452
+		$mustLocks = array_values($tmp);
453
+
454
+		foreach ($conditions as $kk => $condition) {
455
+			foreach ($condition['tokens'] as $ii => $token) {
456
+				// Lock tokens always start with opaquelocktoken:
457
+				if ('opaquelocktoken:' !== substr($token['token'], 0, 16)) {
458
+					continue;
459
+				}
460
+
461
+				$checkToken = substr($token['token'], 16);
462
+				// Looping through our list with locks.
463
+				foreach ($mustLocks as $jj => $mustLock) {
464
+					if ($mustLock->token == $checkToken) {
465
+						// We have a match!
466
+						// Removing this one from mustlocks
467
+						unset($mustLocks[$jj]);
468
+
469
+						// Marking the condition as valid.
470
+						$conditions[$kk]['tokens'][$ii]['validToken'] = true;
471
+
472
+						// Advancing to the next token
473
+						continue 2;
474
+					}
475
+				}
476
+
477
+				// If we got here, it means that there was a
478
+				// lock-token, but it was not in 'mustLocks'.
479
+				//
480
+				// This is an edge-case, as it could mean that token
481
+				// was specified with a url that was not 'required' to
482
+				// check. So we're doing one extra lookup to make sure
483
+				// we really don't know this token.
484
+				//
485
+				// This also gets triggered when the user specified a
486
+				// lock-token that was expired.
487
+				$oddLocks = $this->getLocks($condition['uri']);
488
+				foreach ($oddLocks as $oddLock) {
489
+					if ($oddLock->token === $checkToken) {
490
+						// We have a hit!
491
+						$conditions[$kk]['tokens'][$ii]['validToken'] = true;
492
+						continue 2;
493
+					}
494
+				}
495
+
496
+				// If we get all the way here, the lock-token was
497
+				// really unknown.
498
+			}
499
+		}
500
+
501
+		// If there's any locks left in the 'mustLocks' array, it means that
502
+		// the resource was locked and we must block it.
503
+		if ($mustLocks) {
504
+			throw new DAV\Exception\Locked(reset($mustLocks));
505
+		}
506
+	}
507
+
508
+	/**
509
+	 * Parses a webdav lock xml body, and returns a new Sabre\DAV\Locks\LockInfo object.
510
+	 *
511
+	 * @param string $body
512
+	 *
513
+	 * @return LockInfo
514
+	 */
515
+	protected function parseLockRequest($body)
516
+	{
517
+		$result = $this->server->xml->expect(
518
+			'{DAV:}lockinfo',
519
+			$body
520
+		);
521
+
522
+		$lockInfo = new LockInfo();
523
+
524
+		$lockInfo->owner = $result->owner;
525
+		$lockInfo->token = DAV\UUIDUtil::getUUID();
526
+		$lockInfo->scope = $result->scope;
527
+
528
+		return $lockInfo;
529
+	}
530
+
531
+	/**
532
+	 * Returns a bunch of meta-data about the plugin.
533
+	 *
534
+	 * Providing this information is optional, and is mainly displayed by the
535
+	 * Browser plugin.
536
+	 *
537
+	 * The description key in the returned array may contain html and will not
538
+	 * be sanitized.
539
+	 *
540
+	 * @return array
541
+	 */
542
+	public function getPluginInfo()
543
+	{
544
+		return [
545
+			'name' => $this->getPluginName(),
546
+			'description' => 'The locks plugin turns this server into a class-2 WebDAV server and adds support for LOCK and UNLOCK',
547
+			'link' => 'http://sabre.io/dav/locks/',
548
+		];
549
+	}
550 550
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -83,10 +83,10 @@
 block discarded – undo
83 83
      */
84 84
     public function propFind(DAV\PropFind $propFind, DAV\INode $node)
85 85
     {
86
-        $propFind->handle('{DAV:}supportedlock', function () {
86
+        $propFind->handle('{DAV:}supportedlock', function() {
87 87
             return new DAV\Xml\Property\SupportedLock();
88 88
         });
89
-        $propFind->handle('{DAV:}lockdiscovery', function () use ($propFind) {
89
+        $propFind->handle('{DAV:}lockdiscovery', function() use ($propFind) {
90 90
             return new DAV\Xml\Property\LockDiscovery(
91 91
                 $this->getLocks($propFind->getPath())
92 92
             );
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/DAV/Locks/Backend/BackendInterface.php 1 patch
Indentation   +31 added lines, -31 removed lines patch added patch discarded remove patch
@@ -16,37 +16,37 @@
 block discarded – undo
16 16
  */
17 17
 interface BackendInterface
18 18
 {
19
-    /**
20
-     * Returns a list of Sabre\DAV\Locks\LockInfo objects.
21
-     *
22
-     * This method should return all the locks for a particular uri, including
23
-     * locks that might be set on a parent uri.
24
-     *
25
-     * If returnChildLocks is set to true, this method should also look for
26
-     * any locks in the subtree of the uri for locks.
27
-     *
28
-     * @param string $uri
29
-     * @param bool   $returnChildLocks
30
-     *
31
-     * @return array
32
-     */
33
-    public function getLocks($uri, $returnChildLocks);
19
+	/**
20
+	 * Returns a list of Sabre\DAV\Locks\LockInfo objects.
21
+	 *
22
+	 * This method should return all the locks for a particular uri, including
23
+	 * locks that might be set on a parent uri.
24
+	 *
25
+	 * If returnChildLocks is set to true, this method should also look for
26
+	 * any locks in the subtree of the uri for locks.
27
+	 *
28
+	 * @param string $uri
29
+	 * @param bool   $returnChildLocks
30
+	 *
31
+	 * @return array
32
+	 */
33
+	public function getLocks($uri, $returnChildLocks);
34 34
 
35
-    /**
36
-     * Locks a uri.
37
-     *
38
-     * @param string $uri
39
-     *
40
-     * @return bool
41
-     */
42
-    public function lock($uri, Locks\LockInfo $lockInfo);
35
+	/**
36
+	 * Locks a uri.
37
+	 *
38
+	 * @param string $uri
39
+	 *
40
+	 * @return bool
41
+	 */
42
+	public function lock($uri, Locks\LockInfo $lockInfo);
43 43
 
44
-    /**
45
-     * Removes a lock from a uri.
46
-     *
47
-     * @param string $uri
48
-     *
49
-     * @return bool
50
-     */
51
-    public function unlock($uri, Locks\LockInfo $lockInfo);
44
+	/**
45
+	 * Removes a lock from a uri.
46
+	 *
47
+	 * @param string $uri
48
+	 *
49
+	 * @return bool
50
+	 */
51
+	public function unlock($uri, Locks\LockInfo $lockInfo);
52 52
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/DAV/Locks/Backend/File.php 1 patch
Indentation   +158 added lines, -158 removed lines patch added patch discarded remove patch
@@ -21,162 +21,162 @@
 block discarded – undo
21 21
  */
22 22
 class File extends AbstractBackend
23 23
 {
24
-    /**
25
-     * The storage file.
26
-     *
27
-     * @var string
28
-     */
29
-    private $locksFile;
30
-
31
-    /**
32
-     * Constructor.
33
-     *
34
-     * @param string $locksFile path to file
35
-     */
36
-    public function __construct($locksFile)
37
-    {
38
-        $this->locksFile = $locksFile;
39
-    }
40
-
41
-    /**
42
-     * Returns a list of Sabre\DAV\Locks\LockInfo objects.
43
-     *
44
-     * This method should return all the locks for a particular uri, including
45
-     * locks that might be set on a parent uri.
46
-     *
47
-     * If returnChildLocks is set to true, this method should also look for
48
-     * any locks in the subtree of the uri for locks.
49
-     *
50
-     * @param string $uri
51
-     * @param bool   $returnChildLocks
52
-     *
53
-     * @return array
54
-     */
55
-    public function getLocks($uri, $returnChildLocks)
56
-    {
57
-        $newLocks = [];
58
-
59
-        $locks = $this->getData();
60
-
61
-        foreach ($locks as $lock) {
62
-            if ($lock->uri === $uri ||
63
-                //deep locks on parents
64
-                (0 != $lock->depth && 0 === strpos($uri, $lock->uri.'/')) ||
65
-
66
-                // locks on children
67
-                ($returnChildLocks && (0 === strpos($lock->uri, $uri.'/')))) {
68
-                $newLocks[] = $lock;
69
-            }
70
-        }
71
-
72
-        // Checking if we can remove any of these locks
73
-        foreach ($newLocks as $k => $lock) {
74
-            if (time() > $lock->timeout + $lock->created) {
75
-                unset($newLocks[$k]);
76
-            }
77
-        }
78
-
79
-        return $newLocks;
80
-    }
81
-
82
-    /**
83
-     * Locks a uri.
84
-     *
85
-     * @param string $uri
86
-     *
87
-     * @return bool
88
-     */
89
-    public function lock($uri, LockInfo $lockInfo)
90
-    {
91
-        // We're making the lock timeout 30 minutes
92
-        $lockInfo->timeout = 1800;
93
-        $lockInfo->created = time();
94
-        $lockInfo->uri = $uri;
95
-
96
-        $locks = $this->getData();
97
-
98
-        foreach ($locks as $k => $lock) {
99
-            if (
100
-                ($lock->token == $lockInfo->token) ||
101
-                (time() > $lock->timeout + $lock->created)
102
-            ) {
103
-                unset($locks[$k]);
104
-            }
105
-        }
106
-        $locks[] = $lockInfo;
107
-        $this->putData($locks);
108
-
109
-        return true;
110
-    }
111
-
112
-    /**
113
-     * Removes a lock from a uri.
114
-     *
115
-     * @param string $uri
116
-     *
117
-     * @return bool
118
-     */
119
-    public function unlock($uri, LockInfo $lockInfo)
120
-    {
121
-        $locks = $this->getData();
122
-        foreach ($locks as $k => $lock) {
123
-            if ($lock->token == $lockInfo->token) {
124
-                unset($locks[$k]);
125
-                $this->putData($locks);
126
-
127
-                return true;
128
-            }
129
-        }
130
-
131
-        return false;
132
-    }
133
-
134
-    /**
135
-     * Loads the lockdata from the filesystem.
136
-     *
137
-     * @return array
138
-     */
139
-    protected function getData()
140
-    {
141
-        if (!file_exists($this->locksFile)) {
142
-            return [];
143
-        }
144
-
145
-        // opening up the file, and creating a shared lock
146
-        $handle = fopen($this->locksFile, 'r');
147
-        flock($handle, LOCK_SH);
148
-
149
-        // Reading data until the eof
150
-        $data = stream_get_contents($handle);
151
-
152
-        // We're all good
153
-        flock($handle, LOCK_UN);
154
-        fclose($handle);
155
-
156
-        // Unserializing and checking if the resource file contains data for this file
157
-        $data = unserialize($data);
158
-        if (!$data) {
159
-            return [];
160
-        }
161
-
162
-        return $data;
163
-    }
164
-
165
-    /**
166
-     * Saves the lockdata.
167
-     */
168
-    protected function putData(array $newData)
169
-    {
170
-        // opening up the file, and creating an exclusive lock
171
-        $handle = fopen($this->locksFile, 'a+');
172
-        flock($handle, LOCK_EX);
173
-
174
-        // We can only truncate and rewind once the lock is acquired.
175
-        ftruncate($handle, 0);
176
-        rewind($handle);
177
-
178
-        fwrite($handle, serialize($newData));
179
-        flock($handle, LOCK_UN);
180
-        fclose($handle);
181
-    }
24
+	/**
25
+	 * The storage file.
26
+	 *
27
+	 * @var string
28
+	 */
29
+	private $locksFile;
30
+
31
+	/**
32
+	 * Constructor.
33
+	 *
34
+	 * @param string $locksFile path to file
35
+	 */
36
+	public function __construct($locksFile)
37
+	{
38
+		$this->locksFile = $locksFile;
39
+	}
40
+
41
+	/**
42
+	 * Returns a list of Sabre\DAV\Locks\LockInfo objects.
43
+	 *
44
+	 * This method should return all the locks for a particular uri, including
45
+	 * locks that might be set on a parent uri.
46
+	 *
47
+	 * If returnChildLocks is set to true, this method should also look for
48
+	 * any locks in the subtree of the uri for locks.
49
+	 *
50
+	 * @param string $uri
51
+	 * @param bool   $returnChildLocks
52
+	 *
53
+	 * @return array
54
+	 */
55
+	public function getLocks($uri, $returnChildLocks)
56
+	{
57
+		$newLocks = [];
58
+
59
+		$locks = $this->getData();
60
+
61
+		foreach ($locks as $lock) {
62
+			if ($lock->uri === $uri ||
63
+				//deep locks on parents
64
+				(0 != $lock->depth && 0 === strpos($uri, $lock->uri.'/')) ||
65
+
66
+				// locks on children
67
+				($returnChildLocks && (0 === strpos($lock->uri, $uri.'/')))) {
68
+				$newLocks[] = $lock;
69
+			}
70
+		}
71
+
72
+		// Checking if we can remove any of these locks
73
+		foreach ($newLocks as $k => $lock) {
74
+			if (time() > $lock->timeout + $lock->created) {
75
+				unset($newLocks[$k]);
76
+			}
77
+		}
78
+
79
+		return $newLocks;
80
+	}
81
+
82
+	/**
83
+	 * Locks a uri.
84
+	 *
85
+	 * @param string $uri
86
+	 *
87
+	 * @return bool
88
+	 */
89
+	public function lock($uri, LockInfo $lockInfo)
90
+	{
91
+		// We're making the lock timeout 30 minutes
92
+		$lockInfo->timeout = 1800;
93
+		$lockInfo->created = time();
94
+		$lockInfo->uri = $uri;
95
+
96
+		$locks = $this->getData();
97
+
98
+		foreach ($locks as $k => $lock) {
99
+			if (
100
+				($lock->token == $lockInfo->token) ||
101
+				(time() > $lock->timeout + $lock->created)
102
+			) {
103
+				unset($locks[$k]);
104
+			}
105
+		}
106
+		$locks[] = $lockInfo;
107
+		$this->putData($locks);
108
+
109
+		return true;
110
+	}
111
+
112
+	/**
113
+	 * Removes a lock from a uri.
114
+	 *
115
+	 * @param string $uri
116
+	 *
117
+	 * @return bool
118
+	 */
119
+	public function unlock($uri, LockInfo $lockInfo)
120
+	{
121
+		$locks = $this->getData();
122
+		foreach ($locks as $k => $lock) {
123
+			if ($lock->token == $lockInfo->token) {
124
+				unset($locks[$k]);
125
+				$this->putData($locks);
126
+
127
+				return true;
128
+			}
129
+		}
130
+
131
+		return false;
132
+	}
133
+
134
+	/**
135
+	 * Loads the lockdata from the filesystem.
136
+	 *
137
+	 * @return array
138
+	 */
139
+	protected function getData()
140
+	{
141
+		if (!file_exists($this->locksFile)) {
142
+			return [];
143
+		}
144
+
145
+		// opening up the file, and creating a shared lock
146
+		$handle = fopen($this->locksFile, 'r');
147
+		flock($handle, LOCK_SH);
148
+
149
+		// Reading data until the eof
150
+		$data = stream_get_contents($handle);
151
+
152
+		// We're all good
153
+		flock($handle, LOCK_UN);
154
+		fclose($handle);
155
+
156
+		// Unserializing and checking if the resource file contains data for this file
157
+		$data = unserialize($data);
158
+		if (!$data) {
159
+			return [];
160
+		}
161
+
162
+		return $data;
163
+	}
164
+
165
+	/**
166
+	 * Saves the lockdata.
167
+	 */
168
+	protected function putData(array $newData)
169
+	{
170
+		// opening up the file, and creating an exclusive lock
171
+		$handle = fopen($this->locksFile, 'a+');
172
+		flock($handle, LOCK_EX);
173
+
174
+		// We can only truncate and rewind once the lock is acquired.
175
+		ftruncate($handle, 0);
176
+		rewind($handle);
177
+
178
+		fwrite($handle, serialize($newData));
179
+		flock($handle, LOCK_UN);
180
+		fclose($handle);
181
+	}
182 182
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/DAV/Locks/Backend/PDO.php 1 patch
Indentation   +151 added lines, -151 removed lines patch added patch discarded remove patch
@@ -18,155 +18,155 @@
 block discarded – undo
18 18
  */
19 19
 class PDO extends AbstractBackend
20 20
 {
21
-    /**
22
-     * The PDO tablename this backend uses.
23
-     *
24
-     * @var string
25
-     */
26
-    public $tableName = 'locks';
27
-
28
-    /**
29
-     * The PDO connection object.
30
-     *
31
-     * @var pdo
32
-     */
33
-    protected $pdo;
34
-
35
-    /**
36
-     * Constructor.
37
-     */
38
-    public function __construct(\PDO $pdo)
39
-    {
40
-        $this->pdo = $pdo;
41
-    }
42
-
43
-    /**
44
-     * Returns a list of Sabre\DAV\Locks\LockInfo objects.
45
-     *
46
-     * This method should return all the locks for a particular uri, including
47
-     * locks that might be set on a parent uri.
48
-     *
49
-     * If returnChildLocks is set to true, this method should also look for
50
-     * any locks in the subtree of the uri for locks.
51
-     *
52
-     * @param string $uri
53
-     * @param bool   $returnChildLocks
54
-     *
55
-     * @return array
56
-     */
57
-    public function getLocks($uri, $returnChildLocks)
58
-    {
59
-        // NOTE: the following 10 lines or so could be easily replaced by
60
-        // pure sql. MySQL's non-standard string concatenation prevents us
61
-        // from doing this though.
62
-        $query = 'SELECT owner, token, timeout, created, scope, depth, uri FROM '.$this->tableName.' WHERE (created > (? - timeout)) AND ((uri = ?)';
63
-        $params = [time(), $uri];
64
-
65
-        // We need to check locks for every part in the uri.
66
-        $uriParts = explode('/', $uri);
67
-
68
-        // We already covered the last part of the uri
69
-        array_pop($uriParts);
70
-
71
-        $currentPath = '';
72
-
73
-        foreach ($uriParts as $part) {
74
-            if ($currentPath) {
75
-                $currentPath .= '/';
76
-            }
77
-            $currentPath .= $part;
78
-
79
-            $query .= ' OR (depth!=0 AND uri = ?)';
80
-            $params[] = $currentPath;
81
-        }
82
-
83
-        if ($returnChildLocks) {
84
-            $query .= ' OR (uri LIKE ?)';
85
-            $params[] = $uri.'/%';
86
-        }
87
-        $query .= ')';
88
-
89
-        $stmt = $this->pdo->prepare($query);
90
-        $stmt->execute($params);
91
-        $result = $stmt->fetchAll();
92
-
93
-        $lockList = [];
94
-        foreach ($result as $row) {
95
-            $lockInfo = new LockInfo();
96
-            $lockInfo->owner = $row['owner'];
97
-            $lockInfo->token = $row['token'];
98
-            $lockInfo->timeout = $row['timeout'];
99
-            $lockInfo->created = $row['created'];
100
-            $lockInfo->scope = $row['scope'];
101
-            $lockInfo->depth = $row['depth'];
102
-            $lockInfo->uri = $row['uri'];
103
-            $lockList[] = $lockInfo;
104
-        }
105
-
106
-        return $lockList;
107
-    }
108
-
109
-    /**
110
-     * Locks a uri.
111
-     *
112
-     * @param string $uri
113
-     *
114
-     * @return bool
115
-     */
116
-    public function lock($uri, LockInfo $lockInfo)
117
-    {
118
-        // We're making the lock timeout 30 minutes
119
-        $lockInfo->timeout = 30 * 60;
120
-        $lockInfo->created = time();
121
-        $lockInfo->uri = $uri;
122
-
123
-        $locks = $this->getLocks($uri, false);
124
-        $exists = false;
125
-        foreach ($locks as $lock) {
126
-            if ($lock->token == $lockInfo->token) {
127
-                $exists = true;
128
-            }
129
-        }
130
-
131
-        if ($exists) {
132
-            $stmt = $this->pdo->prepare('UPDATE '.$this->tableName.' SET owner = ?, timeout = ?, scope = ?, depth = ?, uri = ?, created = ? WHERE token = ?');
133
-            $stmt->execute([
134
-                $lockInfo->owner,
135
-                $lockInfo->timeout,
136
-                $lockInfo->scope,
137
-                $lockInfo->depth,
138
-                $uri,
139
-                $lockInfo->created,
140
-                $lockInfo->token,
141
-            ]);
142
-        } else {
143
-            $stmt = $this->pdo->prepare('INSERT INTO '.$this->tableName.' (owner,timeout,scope,depth,uri,created,token) VALUES (?,?,?,?,?,?,?)');
144
-            $stmt->execute([
145
-                $lockInfo->owner,
146
-                $lockInfo->timeout,
147
-                $lockInfo->scope,
148
-                $lockInfo->depth,
149
-                $uri,
150
-                $lockInfo->created,
151
-                $lockInfo->token,
152
-            ]);
153
-        }
154
-
155
-        return true;
156
-    }
157
-
158
-    /**
159
-     * Removes a lock from a uri.
160
-     *
161
-     * @param string $uri
162
-     *
163
-     * @return bool
164
-     */
165
-    public function unlock($uri, LockInfo $lockInfo)
166
-    {
167
-        $stmt = $this->pdo->prepare('DELETE FROM '.$this->tableName.' WHERE uri = ? AND token = ?');
168
-        $stmt->execute([$uri, $lockInfo->token]);
169
-
170
-        return 1 === $stmt->rowCount();
171
-    }
21
+	/**
22
+	 * The PDO tablename this backend uses.
23
+	 *
24
+	 * @var string
25
+	 */
26
+	public $tableName = 'locks';
27
+
28
+	/**
29
+	 * The PDO connection object.
30
+	 *
31
+	 * @var pdo
32
+	 */
33
+	protected $pdo;
34
+
35
+	/**
36
+	 * Constructor.
37
+	 */
38
+	public function __construct(\PDO $pdo)
39
+	{
40
+		$this->pdo = $pdo;
41
+	}
42
+
43
+	/**
44
+	 * Returns a list of Sabre\DAV\Locks\LockInfo objects.
45
+	 *
46
+	 * This method should return all the locks for a particular uri, including
47
+	 * locks that might be set on a parent uri.
48
+	 *
49
+	 * If returnChildLocks is set to true, this method should also look for
50
+	 * any locks in the subtree of the uri for locks.
51
+	 *
52
+	 * @param string $uri
53
+	 * @param bool   $returnChildLocks
54
+	 *
55
+	 * @return array
56
+	 */
57
+	public function getLocks($uri, $returnChildLocks)
58
+	{
59
+		// NOTE: the following 10 lines or so could be easily replaced by
60
+		// pure sql. MySQL's non-standard string concatenation prevents us
61
+		// from doing this though.
62
+		$query = 'SELECT owner, token, timeout, created, scope, depth, uri FROM '.$this->tableName.' WHERE (created > (? - timeout)) AND ((uri = ?)';
63
+		$params = [time(), $uri];
64
+
65
+		// We need to check locks for every part in the uri.
66
+		$uriParts = explode('/', $uri);
67
+
68
+		// We already covered the last part of the uri
69
+		array_pop($uriParts);
70
+
71
+		$currentPath = '';
72
+
73
+		foreach ($uriParts as $part) {
74
+			if ($currentPath) {
75
+				$currentPath .= '/';
76
+			}
77
+			$currentPath .= $part;
78
+
79
+			$query .= ' OR (depth!=0 AND uri = ?)';
80
+			$params[] = $currentPath;
81
+		}
82
+
83
+		if ($returnChildLocks) {
84
+			$query .= ' OR (uri LIKE ?)';
85
+			$params[] = $uri.'/%';
86
+		}
87
+		$query .= ')';
88
+
89
+		$stmt = $this->pdo->prepare($query);
90
+		$stmt->execute($params);
91
+		$result = $stmt->fetchAll();
92
+
93
+		$lockList = [];
94
+		foreach ($result as $row) {
95
+			$lockInfo = new LockInfo();
96
+			$lockInfo->owner = $row['owner'];
97
+			$lockInfo->token = $row['token'];
98
+			$lockInfo->timeout = $row['timeout'];
99
+			$lockInfo->created = $row['created'];
100
+			$lockInfo->scope = $row['scope'];
101
+			$lockInfo->depth = $row['depth'];
102
+			$lockInfo->uri = $row['uri'];
103
+			$lockList[] = $lockInfo;
104
+		}
105
+
106
+		return $lockList;
107
+	}
108
+
109
+	/**
110
+	 * Locks a uri.
111
+	 *
112
+	 * @param string $uri
113
+	 *
114
+	 * @return bool
115
+	 */
116
+	public function lock($uri, LockInfo $lockInfo)
117
+	{
118
+		// We're making the lock timeout 30 minutes
119
+		$lockInfo->timeout = 30 * 60;
120
+		$lockInfo->created = time();
121
+		$lockInfo->uri = $uri;
122
+
123
+		$locks = $this->getLocks($uri, false);
124
+		$exists = false;
125
+		foreach ($locks as $lock) {
126
+			if ($lock->token == $lockInfo->token) {
127
+				$exists = true;
128
+			}
129
+		}
130
+
131
+		if ($exists) {
132
+			$stmt = $this->pdo->prepare('UPDATE '.$this->tableName.' SET owner = ?, timeout = ?, scope = ?, depth = ?, uri = ?, created = ? WHERE token = ?');
133
+			$stmt->execute([
134
+				$lockInfo->owner,
135
+				$lockInfo->timeout,
136
+				$lockInfo->scope,
137
+				$lockInfo->depth,
138
+				$uri,
139
+				$lockInfo->created,
140
+				$lockInfo->token,
141
+			]);
142
+		} else {
143
+			$stmt = $this->pdo->prepare('INSERT INTO '.$this->tableName.' (owner,timeout,scope,depth,uri,created,token) VALUES (?,?,?,?,?,?,?)');
144
+			$stmt->execute([
145
+				$lockInfo->owner,
146
+				$lockInfo->timeout,
147
+				$lockInfo->scope,
148
+				$lockInfo->depth,
149
+				$uri,
150
+				$lockInfo->created,
151
+				$lockInfo->token,
152
+			]);
153
+		}
154
+
155
+		return true;
156
+	}
157
+
158
+	/**
159
+	 * Removes a lock from a uri.
160
+	 *
161
+	 * @param string $uri
162
+	 *
163
+	 * @return bool
164
+	 */
165
+	public function unlock($uri, LockInfo $lockInfo)
166
+	{
167
+		$stmt = $this->pdo->prepare('DELETE FROM '.$this->tableName.' WHERE uri = ? AND token = ?');
168
+		$stmt->execute([$uri, $lockInfo->token]);
169
+
170
+		return 1 === $stmt->rowCount();
171
+	}
172 172
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/DAV/Locks/LockInfo.php 1 patch
Indentation   +54 added lines, -54 removed lines patch added patch discarded remove patch
@@ -16,67 +16,67 @@
 block discarded – undo
16 16
  */
17 17
 class LockInfo
18 18
 {
19
-    /**
20
-     * A shared lock.
21
-     */
22
-    const SHARED = 1;
19
+	/**
20
+	 * A shared lock.
21
+	 */
22
+	const SHARED = 1;
23 23
 
24
-    /**
25
-     * An exclusive lock.
26
-     */
27
-    const EXCLUSIVE = 2;
24
+	/**
25
+	 * An exclusive lock.
26
+	 */
27
+	const EXCLUSIVE = 2;
28 28
 
29
-    /**
30
-     * A never expiring timeout.
31
-     */
32
-    const TIMEOUT_INFINITE = -1;
29
+	/**
30
+	 * A never expiring timeout.
31
+	 */
32
+	const TIMEOUT_INFINITE = -1;
33 33
 
34
-    /**
35
-     * The owner of the lock.
36
-     *
37
-     * @var string
38
-     */
39
-    public $owner;
34
+	/**
35
+	 * The owner of the lock.
36
+	 *
37
+	 * @var string
38
+	 */
39
+	public $owner;
40 40
 
41
-    /**
42
-     * The locktoken.
43
-     *
44
-     * @var string
45
-     */
46
-    public $token;
41
+	/**
42
+	 * The locktoken.
43
+	 *
44
+	 * @var string
45
+	 */
46
+	public $token;
47 47
 
48
-    /**
49
-     * How long till the lock is expiring.
50
-     *
51
-     * @var int
52
-     */
53
-    public $timeout;
48
+	/**
49
+	 * How long till the lock is expiring.
50
+	 *
51
+	 * @var int
52
+	 */
53
+	public $timeout;
54 54
 
55
-    /**
56
-     * UNIX Timestamp of when this lock was created.
57
-     *
58
-     * @var int
59
-     */
60
-    public $created;
55
+	/**
56
+	 * UNIX Timestamp of when this lock was created.
57
+	 *
58
+	 * @var int
59
+	 */
60
+	public $created;
61 61
 
62
-    /**
63
-     * Exclusive or shared lock.
64
-     *
65
-     * @var int
66
-     */
67
-    public $scope = self::EXCLUSIVE;
62
+	/**
63
+	 * Exclusive or shared lock.
64
+	 *
65
+	 * @var int
66
+	 */
67
+	public $scope = self::EXCLUSIVE;
68 68
 
69
-    /**
70
-     * Depth of lock, can be 0 or Sabre\DAV\Server::DEPTH_INFINITY.
71
-     */
72
-    public $depth = 0;
69
+	/**
70
+	 * Depth of lock, can be 0 or Sabre\DAV\Server::DEPTH_INFINITY.
71
+	 */
72
+	public $depth = 0;
73 73
 
74
-    /**
75
-     * The uri this lock locks.
76
-     *
77
-     * TODO: This value is not always set
78
-     *
79
-     * @var mixed
80
-     */
81
-    public $uri;
74
+	/**
75
+	 * The uri this lock locks.
76
+	 *
77
+	 * TODO: This value is not always set
78
+	 *
79
+	 * @var mixed
80
+	 */
81
+	public $uri;
82 82
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/DAV/IProperties.php 1 patch
Indentation   +27 added lines, -27 removed lines patch added patch discarded remove patch
@@ -15,32 +15,32 @@
 block discarded – undo
15 15
  */
16 16
 interface IProperties extends INode
17 17
 {
18
-    /**
19
-     * Updates properties on this node.
20
-     *
21
-     * This method received a PropPatch object, which contains all the
22
-     * information about the update.
23
-     *
24
-     * To update specific properties, call the 'handle' method on this object.
25
-     * Read the PropPatch documentation for more information.
26
-     */
27
-    public function propPatch(PropPatch $propPatch);
18
+	/**
19
+	 * Updates properties on this node.
20
+	 *
21
+	 * This method received a PropPatch object, which contains all the
22
+	 * information about the update.
23
+	 *
24
+	 * To update specific properties, call the 'handle' method on this object.
25
+	 * Read the PropPatch documentation for more information.
26
+	 */
27
+	public function propPatch(PropPatch $propPatch);
28 28
 
29
-    /**
30
-     * Returns a list of properties for this nodes.
31
-     *
32
-     * The properties list is a list of propertynames the client requested,
33
-     * encoded in clark-notation {xmlnamespace}tagname
34
-     *
35
-     * If the array is empty, it means 'all properties' were requested.
36
-     *
37
-     * Note that it's fine to liberally give properties back, instead of
38
-     * conforming to the list of requested properties.
39
-     * The Server class will filter out the extra.
40
-     *
41
-     * @param array $properties
42
-     *
43
-     * @return array
44
-     */
45
-    public function getProperties($properties);
29
+	/**
30
+	 * Returns a list of properties for this nodes.
31
+	 *
32
+	 * The properties list is a list of propertynames the client requested,
33
+	 * encoded in clark-notation {xmlnamespace}tagname
34
+	 *
35
+	 * If the array is empty, it means 'all properties' were requested.
36
+	 *
37
+	 * Note that it's fine to liberally give properties back, instead of
38
+	 * conforming to the list of requested properties.
39
+	 * The Server class will filter out the extra.
40
+	 *
41
+	 * @param array $properties
42
+	 *
43
+	 * @return array
44
+	 */
45
+	public function getProperties($properties);
46 46
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/DAV/Auth/Plugin.php 1 patch
Indentation   +226 added lines, -226 removed lines patch added patch discarded remove patch
@@ -26,230 +26,230 @@
 block discarded – undo
26 26
  */
27 27
 class Plugin extends ServerPlugin
28 28
 {
29
-    /**
30
-     * By default this plugin will require that the user is authenticated,
31
-     * and refuse any access if the user is not authenticated.
32
-     *
33
-     * If this setting is set to false, we let the user through, whether they
34
-     * are authenticated or not.
35
-     *
36
-     * This is useful if you want to allow both authenticated and
37
-     * unauthenticated access to your server.
38
-     *
39
-     * @param bool
40
-     */
41
-    public $autoRequireLogin = true;
42
-
43
-    /**
44
-     * authentication backends.
45
-     */
46
-    protected $backends;
47
-
48
-    /**
49
-     * The currently logged in principal. Will be `null` if nobody is currently
50
-     * logged in.
51
-     *
52
-     * @var string|null
53
-     */
54
-    protected $currentPrincipal;
55
-
56
-    /**
57
-     * Creates the authentication plugin.
58
-     *
59
-     * @param Backend\BackendInterface $authBackend
60
-     */
61
-    public function __construct(Backend\BackendInterface $authBackend = null)
62
-    {
63
-        if (!is_null($authBackend)) {
64
-            $this->addBackend($authBackend);
65
-        }
66
-    }
67
-
68
-    /**
69
-     * Adds an authentication backend to the plugin.
70
-     */
71
-    public function addBackend(Backend\BackendInterface $authBackend)
72
-    {
73
-        $this->backends[] = $authBackend;
74
-    }
75
-
76
-    /**
77
-     * Initializes the plugin. This function is automatically called by the server.
78
-     */
79
-    public function initialize(Server $server)
80
-    {
81
-        $server->on('beforeMethod:*', [$this, 'beforeMethod'], 10);
82
-    }
83
-
84
-    /**
85
-     * Returns a plugin name.
86
-     *
87
-     * Using this name other plugins will be able to access other plugins
88
-     * using DAV\Server::getPlugin
89
-     *
90
-     * @return string
91
-     */
92
-    public function getPluginName()
93
-    {
94
-        return 'auth';
95
-    }
96
-
97
-    /**
98
-     * Returns the currently logged-in principal.
99
-     *
100
-     * This will return a string such as:
101
-     *
102
-     * principals/username
103
-     * principals/users/username
104
-     *
105
-     * This method will return null if nobody is logged in.
106
-     *
107
-     * @return string|null
108
-     */
109
-    public function getCurrentPrincipal()
110
-    {
111
-        return $this->currentPrincipal;
112
-    }
113
-
114
-    /**
115
-     * This method is called before any HTTP method and forces users to be authenticated.
116
-     */
117
-    public function beforeMethod(RequestInterface $request, ResponseInterface $response)
118
-    {
119
-        if ($this->currentPrincipal) {
120
-            // We already have authentication information. This means that the
121
-            // event has already fired earlier, and is now likely fired for a
122
-            // sub-request.
123
-            //
124
-            // We don't want to authenticate users twice, so we simply don't do
125
-            // anything here. See Issue #700 for additional reasoning.
126
-            //
127
-            // This is not a perfect solution, but will be fixed once the
128
-            // "currently authenticated principal" is information that's not
129
-            // not associated with the plugin, but rather per-request.
130
-            //
131
-            // See issue #580 for more information about that.
132
-            return;
133
-        }
134
-
135
-        $authResult = $this->check($request, $response);
136
-
137
-        if ($authResult[0]) {
138
-            // Auth was successful
139
-            $this->currentPrincipal = $authResult[1];
140
-            $this->loginFailedReasons = null;
141
-
142
-            return;
143
-        }
144
-
145
-        // If we got here, it means that no authentication backend was
146
-        // successful in authenticating the user.
147
-        $this->currentPrincipal = null;
148
-        $this->loginFailedReasons = $authResult[1];
149
-
150
-        if ($this->autoRequireLogin) {
151
-            $this->challenge($request, $response);
152
-            throw new NotAuthenticated(implode(', ', $authResult[1]));
153
-        }
154
-    }
155
-
156
-    /**
157
-     * Checks authentication credentials, and logs the user in if possible.
158
-     *
159
-     * This method returns an array. The first item in the array is a boolean
160
-     * indicating if login was successful.
161
-     *
162
-     * If login was successful, the second item in the array will contain the
163
-     * current principal url/path of the logged in user.
164
-     *
165
-     * If login was not successful, the second item in the array will contain a
166
-     * an array with strings. The strings are a list of reasons why login was
167
-     * unsuccessful. For every auth backend there will be one reason, so usually
168
-     * there's just one.
169
-     *
170
-     * @return array
171
-     */
172
-    public function check(RequestInterface $request, ResponseInterface $response)
173
-    {
174
-        if (!$this->backends) {
175
-            throw new \Sabre\DAV\Exception('No authentication backends were configured on this server.');
176
-        }
177
-        $reasons = [];
178
-        foreach ($this->backends as $backend) {
179
-            $result = $backend->check(
180
-                $request,
181
-                $response
182
-            );
183
-
184
-            if (!is_array($result) || 2 !== count($result) || !is_bool($result[0]) || !is_string($result[1])) {
185
-                throw new \Sabre\DAV\Exception('The authentication backend did not return a correct value from the check() method.');
186
-            }
187
-
188
-            if ($result[0]) {
189
-                $this->currentPrincipal = $result[1];
190
-                // Exit early
191
-                return [true, $result[1]];
192
-            }
193
-            $reasons[] = $result[1];
194
-        }
195
-
196
-        return [false, $reasons];
197
-    }
198
-
199
-    /**
200
-     * This method sends authentication challenges to the user.
201
-     *
202
-     * This method will for example cause a HTTP Basic backend to set a
203
-     * WWW-Authorization header, indicating to the client that it should
204
-     * authenticate.
205
-     */
206
-    public function challenge(RequestInterface $request, ResponseInterface $response)
207
-    {
208
-        foreach ($this->backends as $backend) {
209
-            $backend->challenge($request, $response);
210
-        }
211
-    }
212
-
213
-    /**
214
-     * List of reasons why login failed for the last login operation.
215
-     *
216
-     * @var string[]|null
217
-     */
218
-    protected $loginFailedReasons;
219
-
220
-    /**
221
-     * Returns a list of reasons why login was unsuccessful.
222
-     *
223
-     * This method will return the login failed reasons for the last login
224
-     * operation. One for each auth backend.
225
-     *
226
-     * This method returns null if the last authentication attempt was
227
-     * successful, or if there was no authentication attempt yet.
228
-     *
229
-     * @return string[]|null
230
-     */
231
-    public function getLoginFailedReasons()
232
-    {
233
-        return $this->loginFailedReasons;
234
-    }
235
-
236
-    /**
237
-     * Returns a bunch of meta-data about the plugin.
238
-     *
239
-     * Providing this information is optional, and is mainly displayed by the
240
-     * Browser plugin.
241
-     *
242
-     * The description key in the returned array may contain html and will not
243
-     * be sanitized.
244
-     *
245
-     * @return array
246
-     */
247
-    public function getPluginInfo()
248
-    {
249
-        return [
250
-            'name' => $this->getPluginName(),
251
-            'description' => 'Generic authentication plugin',
252
-            'link' => 'http://sabre.io/dav/authentication/',
253
-        ];
254
-    }
29
+	/**
30
+	 * By default this plugin will require that the user is authenticated,
31
+	 * and refuse any access if the user is not authenticated.
32
+	 *
33
+	 * If this setting is set to false, we let the user through, whether they
34
+	 * are authenticated or not.
35
+	 *
36
+	 * This is useful if you want to allow both authenticated and
37
+	 * unauthenticated access to your server.
38
+	 *
39
+	 * @param bool
40
+	 */
41
+	public $autoRequireLogin = true;
42
+
43
+	/**
44
+	 * authentication backends.
45
+	 */
46
+	protected $backends;
47
+
48
+	/**
49
+	 * The currently logged in principal. Will be `null` if nobody is currently
50
+	 * logged in.
51
+	 *
52
+	 * @var string|null
53
+	 */
54
+	protected $currentPrincipal;
55
+
56
+	/**
57
+	 * Creates the authentication plugin.
58
+	 *
59
+	 * @param Backend\BackendInterface $authBackend
60
+	 */
61
+	public function __construct(Backend\BackendInterface $authBackend = null)
62
+	{
63
+		if (!is_null($authBackend)) {
64
+			$this->addBackend($authBackend);
65
+		}
66
+	}
67
+
68
+	/**
69
+	 * Adds an authentication backend to the plugin.
70
+	 */
71
+	public function addBackend(Backend\BackendInterface $authBackend)
72
+	{
73
+		$this->backends[] = $authBackend;
74
+	}
75
+
76
+	/**
77
+	 * Initializes the plugin. This function is automatically called by the server.
78
+	 */
79
+	public function initialize(Server $server)
80
+	{
81
+		$server->on('beforeMethod:*', [$this, 'beforeMethod'], 10);
82
+	}
83
+
84
+	/**
85
+	 * Returns a plugin name.
86
+	 *
87
+	 * Using this name other plugins will be able to access other plugins
88
+	 * using DAV\Server::getPlugin
89
+	 *
90
+	 * @return string
91
+	 */
92
+	public function getPluginName()
93
+	{
94
+		return 'auth';
95
+	}
96
+
97
+	/**
98
+	 * Returns the currently logged-in principal.
99
+	 *
100
+	 * This will return a string such as:
101
+	 *
102
+	 * principals/username
103
+	 * principals/users/username
104
+	 *
105
+	 * This method will return null if nobody is logged in.
106
+	 *
107
+	 * @return string|null
108
+	 */
109
+	public function getCurrentPrincipal()
110
+	{
111
+		return $this->currentPrincipal;
112
+	}
113
+
114
+	/**
115
+	 * This method is called before any HTTP method and forces users to be authenticated.
116
+	 */
117
+	public function beforeMethod(RequestInterface $request, ResponseInterface $response)
118
+	{
119
+		if ($this->currentPrincipal) {
120
+			// We already have authentication information. This means that the
121
+			// event has already fired earlier, and is now likely fired for a
122
+			// sub-request.
123
+			//
124
+			// We don't want to authenticate users twice, so we simply don't do
125
+			// anything here. See Issue #700 for additional reasoning.
126
+			//
127
+			// This is not a perfect solution, but will be fixed once the
128
+			// "currently authenticated principal" is information that's not
129
+			// not associated with the plugin, but rather per-request.
130
+			//
131
+			// See issue #580 for more information about that.
132
+			return;
133
+		}
134
+
135
+		$authResult = $this->check($request, $response);
136
+
137
+		if ($authResult[0]) {
138
+			// Auth was successful
139
+			$this->currentPrincipal = $authResult[1];
140
+			$this->loginFailedReasons = null;
141
+
142
+			return;
143
+		}
144
+
145
+		// If we got here, it means that no authentication backend was
146
+		// successful in authenticating the user.
147
+		$this->currentPrincipal = null;
148
+		$this->loginFailedReasons = $authResult[1];
149
+
150
+		if ($this->autoRequireLogin) {
151
+			$this->challenge($request, $response);
152
+			throw new NotAuthenticated(implode(', ', $authResult[1]));
153
+		}
154
+	}
155
+
156
+	/**
157
+	 * Checks authentication credentials, and logs the user in if possible.
158
+	 *
159
+	 * This method returns an array. The first item in the array is a boolean
160
+	 * indicating if login was successful.
161
+	 *
162
+	 * If login was successful, the second item in the array will contain the
163
+	 * current principal url/path of the logged in user.
164
+	 *
165
+	 * If login was not successful, the second item in the array will contain a
166
+	 * an array with strings. The strings are a list of reasons why login was
167
+	 * unsuccessful. For every auth backend there will be one reason, so usually
168
+	 * there's just one.
169
+	 *
170
+	 * @return array
171
+	 */
172
+	public function check(RequestInterface $request, ResponseInterface $response)
173
+	{
174
+		if (!$this->backends) {
175
+			throw new \Sabre\DAV\Exception('No authentication backends were configured on this server.');
176
+		}
177
+		$reasons = [];
178
+		foreach ($this->backends as $backend) {
179
+			$result = $backend->check(
180
+				$request,
181
+				$response
182
+			);
183
+
184
+			if (!is_array($result) || 2 !== count($result) || !is_bool($result[0]) || !is_string($result[1])) {
185
+				throw new \Sabre\DAV\Exception('The authentication backend did not return a correct value from the check() method.');
186
+			}
187
+
188
+			if ($result[0]) {
189
+				$this->currentPrincipal = $result[1];
190
+				// Exit early
191
+				return [true, $result[1]];
192
+			}
193
+			$reasons[] = $result[1];
194
+		}
195
+
196
+		return [false, $reasons];
197
+	}
198
+
199
+	/**
200
+	 * This method sends authentication challenges to the user.
201
+	 *
202
+	 * This method will for example cause a HTTP Basic backend to set a
203
+	 * WWW-Authorization header, indicating to the client that it should
204
+	 * authenticate.
205
+	 */
206
+	public function challenge(RequestInterface $request, ResponseInterface $response)
207
+	{
208
+		foreach ($this->backends as $backend) {
209
+			$backend->challenge($request, $response);
210
+		}
211
+	}
212
+
213
+	/**
214
+	 * List of reasons why login failed for the last login operation.
215
+	 *
216
+	 * @var string[]|null
217
+	 */
218
+	protected $loginFailedReasons;
219
+
220
+	/**
221
+	 * Returns a list of reasons why login was unsuccessful.
222
+	 *
223
+	 * This method will return the login failed reasons for the last login
224
+	 * operation. One for each auth backend.
225
+	 *
226
+	 * This method returns null if the last authentication attempt was
227
+	 * successful, or if there was no authentication attempt yet.
228
+	 *
229
+	 * @return string[]|null
230
+	 */
231
+	public function getLoginFailedReasons()
232
+	{
233
+		return $this->loginFailedReasons;
234
+	}
235
+
236
+	/**
237
+	 * Returns a bunch of meta-data about the plugin.
238
+	 *
239
+	 * Providing this information is optional, and is mainly displayed by the
240
+	 * Browser plugin.
241
+	 *
242
+	 * The description key in the returned array may contain html and will not
243
+	 * be sanitized.
244
+	 *
245
+	 * @return array
246
+	 */
247
+	public function getPluginInfo()
248
+	{
249
+		return [
250
+			'name' => $this->getPluginName(),
251
+			'description' => 'Generic authentication plugin',
252
+			'link' => 'http://sabre.io/dav/authentication/',
253
+		];
254
+	}
255 255
 }
Please login to merge, or discard this patch.