Completed
Pull Request — developer (#4001)
by Thom
542:26 queued 508:45
created
libraries/SabreDAV/DAV/Server.php 4 patches
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -593,7 +593,7 @@  discard block
 block discarded – undo
593 593
         // If its an unknown value. we'll grab the default
594 594
         if (!ctype_digit($depth)) return $default;
595 595
 
596
-        return (int)$depth;
596
+        return (int) $depth;
597 597
 
598 598
     }
599 599
 
@@ -914,7 +914,7 @@  discard block
 block discarded – undo
914 914
         $path = trim($path, '/');
915 915
 
916 916
         $propFindType = $propertyNames ? PropFind::NORMAL : PropFind::ALLPROPS;
917
-        $propFind = new PropFind($path, (array)$propertyNames, $depth, $propFindType);
917
+        $propFind = new PropFind($path, (array) $propertyNames, $depth, $propFindType);
918 918
 
919 919
         $parentNode = $this->tree->getNodeForPath($path);
920 920
 
@@ -1399,7 +1399,7 @@  discard block
 block discarded – undo
1399 1399
         // Plugins are responsible for validating all the tokens.
1400 1400
         // If a plugin deemed a token 'valid', it will set 'validToken' to
1401 1401
         // true.
1402
-        $this->emit('validateTokens', [ $request, &$ifConditions ]);
1402
+        $this->emit('validateTokens', [$request, &$ifConditions]);
1403 1403
 
1404 1404
         // Now we're going to analyze the result.
1405 1405
 
Please login to merge, or discard this patch.
Indentation   +1601 added lines, -1601 removed lines patch added patch discarded remove patch
@@ -18,1610 +18,1610 @@
 block discarded – undo
18 18
  */
19 19
 class Server extends EventEmitter {
20 20
 
21
-    /**
22
-     * Infinity is used for some request supporting the HTTP Depth header and indicates that the operation should traverse the entire tree
23
-     */
24
-    const DEPTH_INFINITY = -1;
25
-
26
-    /**
27
-     * XML namespace for all SabreDAV related elements
28
-     */
29
-    const NS_SABREDAV = 'http://sabredav.org/ns';
30
-
31
-    /**
32
-     * The tree object
33
-     *
34
-     * @var Sabre\DAV\Tree
35
-     */
36
-    public $tree;
37
-
38
-    /**
39
-     * The base uri
40
-     *
41
-     * @var string
42
-     */
43
-    protected $baseUri = null;
44
-
45
-    /**
46
-     * httpResponse
47
-     *
48
-     * @var Sabre\HTTP\Response
49
-     */
50
-    public $httpResponse;
51
-
52
-    /**
53
-     * httpRequest
54
-     *
55
-     * @var Sabre\HTTP\Request
56
-     */
57
-    public $httpRequest;
58
-
59
-    /**
60
-     * PHP HTTP Sapi
61
-     *
62
-     * @var Sabre\HTTP\Sapi
63
-     */
64
-    public $sapi;
65
-
66
-    /**
67
-     * The list of plugins
68
-     *
69
-     * @var array
70
-     */
71
-    protected $plugins = [];
72
-
73
-    /**
74
-     * This property will be filled with a unique string that describes the
75
-     * transaction. This is useful for performance measuring and logging
76
-     * purposes.
77
-     *
78
-     * By default it will just fill it with a lowercased HTTP method name, but
79
-     * plugins override this. For example, the WebDAV-Sync sync-collection
80
-     * report will set this to 'report-sync-collection'.
81
-     *
82
-     * @var string
83
-     */
84
-    public $transactionType;
85
-
86
-    /**
87
-     * This is a list of properties that are always server-controlled, and
88
-     * must not get modified with PROPPATCH.
89
-     *
90
-     * Plugins may add to this list.
91
-     *
92
-     * @var string[]
93
-     */
94
-    public $protectedProperties = [
95
-
96
-        // RFC4918
97
-        '{DAV:}getcontentlength',
98
-        '{DAV:}getetag',
99
-        '{DAV:}getlastmodified',
100
-        '{DAV:}lockdiscovery',
101
-        '{DAV:}supportedlock',
102
-
103
-        // RFC4331
104
-        '{DAV:}quota-available-bytes',
105
-        '{DAV:}quota-used-bytes',
106
-
107
-        // RFC3744
108
-        '{DAV:}supported-privilege-set',
109
-        '{DAV:}current-user-privilege-set',
110
-        '{DAV:}acl',
111
-        '{DAV:}acl-restrictions',
112
-        '{DAV:}inherited-acl-set',
113
-
114
-        // RFC3253
115
-        '{DAV:}supported-method-set',
116
-        '{DAV:}supported-report-set',
117
-
118
-        // RFC6578
119
-        '{DAV:}sync-token',
120
-
121
-        // calendarserver.org extensions
122
-        '{http://calendarserver.org/ns/}ctag',
123
-
124
-        // sabredav extensions
125
-        '{http://sabredav.org/ns}sync-token',
126
-
127
-    ];
128
-
129
-    /**
130
-     * This is a flag that allow or not showing file, line and code
131
-     * of the exception in the returned XML
132
-     *
133
-     * @var bool
134
-     */
135
-    public $debugExceptions = false;
136
-
137
-    /**
138
-     * This property allows you to automatically add the 'resourcetype' value
139
-     * based on a node's classname or interface.
140
-     *
141
-     * The preset ensures that {DAV:}collection is automatically added for nodes
142
-     * implementing Sabre\DAV\ICollection.
143
-     *
144
-     * @var array
145
-     */
146
-    public $resourceTypeMapping = [
147
-        'Sabre\\DAV\\ICollection' => '{DAV:}collection',
148
-    ];
149
-
150
-    /**
151
-     * This property allows the usage of Depth: infinity on PROPFIND requests.
152
-     *
153
-     * By default Depth: infinity is treated as Depth: 1. Allowing Depth:
154
-     * infinity is potentially risky, as it allows a single client to do a full
155
-     * index of the webdav server, which is an easy DoS attack vector.
156
-     *
157
-     * Only turn this on if you know what you're doing.
158
-     *
159
-     * @var bool
160
-     */
161
-    public $enablePropfindDepthInfinity = false;
162
-
163
-    /**
164
-     * Reference to the XML utility object.
165
-     *
166
-     * @var Xml\Service
167
-     */
168
-    public $xml;
169
-
170
-    /**
171
-     * If this setting is turned off, SabreDAV's version number will be hidden
172
-     * from various places.
173
-     *
174
-     * Some people feel this is a good security measure.
175
-     *
176
-     * @var bool
177
-     */
178
-    static $exposeVersion = true;
179
-
180
-    /**
181
-     * Sets up the server
182
-     *
183
-     * If a Sabre\DAV\Tree object is passed as an argument, it will
184
-     * use it as the directory tree. If a Sabre\DAV\INode is passed, it
185
-     * will create a Sabre\DAV\Tree and use the node as the root.
186
-     *
187
-     * If nothing is passed, a Sabre\DAV\SimpleCollection is created in
188
-     * a Sabre\DAV\Tree.
189
-     *
190
-     * If an array is passed, we automatically create a root node, and use
191
-     * the nodes in the array as top-level children.
192
-     *
193
-     * @param Tree|INode|array|null $treeOrNode The tree object
194
-     */
195
-    public function __construct($treeOrNode = null) {
196
-
197
-        if ($treeOrNode instanceof Tree) {
198
-            $this->tree = $treeOrNode;
199
-        } elseif ($treeOrNode instanceof INode) {
200
-            $this->tree = new Tree($treeOrNode);
201
-        } elseif (is_array($treeOrNode)) {
202
-
203
-            // If it's an array, a list of nodes was passed, and we need to
204
-            // create the root node.
205
-            foreach ($treeOrNode as $node) {
206
-                if (!($node instanceof INode)) {
207
-                    throw new Exception('Invalid argument passed to constructor. If you\'re passing an array, all the values must implement Sabre\\DAV\\INode');
208
-                }
209
-            }
210
-
211
-            $root = new SimpleCollection('root', $treeOrNode);
212
-            $this->tree = new Tree($root);
213
-
214
-        } elseif (is_null($treeOrNode)) {
215
-            $root = new SimpleCollection('root');
216
-            $this->tree = new Tree($root);
217
-        } else {
218
-            throw new Exception('Invalid argument passed to constructor. Argument must either be an instance of Sabre\\DAV\\Tree, Sabre\\DAV\\INode, an array or null');
219
-        }
220
-
221
-        $this->xml = new Xml\Service();
222
-        $this->sapi = new HTTP\Sapi();
223
-        $this->httpResponse = new HTTP\Response();
224
-        $this->httpRequest = $this->sapi->getRequest();
225
-        $this->addPlugin(new CorePlugin());
226
-
227
-    }
228
-
229
-    /**
230
-     * Starts the DAV Server
231
-     *
232
-     * @return void
233
-     */
234
-    public function exec() {
235
-
236
-        try {
237
-
238
-            // If nginx (pre-1.2) is used as a proxy server, and SabreDAV as an
239
-            // origin, we must make sure we send back HTTP/1.0 if this was
240
-            // requested.
241
-            // This is mainly because nginx doesn't support Chunked Transfer
242
-            // Encoding, and this forces the webserver SabreDAV is running on,
243
-            // to buffer entire responses to calculate Content-Length.
244
-            $this->httpResponse->setHTTPVersion($this->httpRequest->getHTTPVersion());
245
-
246
-            // Setting the base url
247
-            $this->httpRequest->setBaseUrl($this->getBaseUri());
248
-            $this->invokeMethod($this->httpRequest, $this->httpResponse);
249
-
250
-        } catch (\Exception $e) {
251
-
252
-            try {
253
-                $this->emit('exception', [$e]);
254
-            } catch (\Exception $ignore) {
255
-            }
256
-            $DOM = new \DOMDocument('1.0', 'utf-8');
257
-            $DOM->formatOutput = true;
258
-
259
-            $error = $DOM->createElementNS('DAV:', 'd:error');
260
-            $error->setAttribute('xmlns:s', self::NS_SABREDAV);
261
-            $DOM->appendChild($error);
262
-
263
-            $h = function($v) {
264
-
265
-                return htmlspecialchars($v, ENT_NOQUOTES, 'UTF-8');
266
-
267
-            };
268
-
269
-            if (self::$exposeVersion) {
270
-                $error->appendChild($DOM->createElement('s:sabredav-version', $h(Version::VERSION)));
271
-            }
272
-
273
-            $error->appendChild($DOM->createElement('s:exception', $h(get_class($e))));
274
-            $error->appendChild($DOM->createElement('s:message', $h($e->getMessage())));
275
-            if ($this->debugExceptions) {
276
-                $error->appendChild($DOM->createElement('s:file', $h($e->getFile())));
277
-                $error->appendChild($DOM->createElement('s:line', $h($e->getLine())));
278
-                $error->appendChild($DOM->createElement('s:code', $h($e->getCode())));
279
-                $error->appendChild($DOM->createElement('s:stacktrace', $h($e->getTraceAsString())));
280
-            }
281
-
282
-            if ($this->debugExceptions) {
283
-                $previous = $e;
284
-                while ($previous = $previous->getPrevious()) {
285
-                    $xPrevious = $DOM->createElement('s:previous-exception');
286
-                    $xPrevious->appendChild($DOM->createElement('s:exception', $h(get_class($previous))));
287
-                    $xPrevious->appendChild($DOM->createElement('s:message', $h($previous->getMessage())));
288
-                    $xPrevious->appendChild($DOM->createElement('s:file', $h($previous->getFile())));
289
-                    $xPrevious->appendChild($DOM->createElement('s:line', $h($previous->getLine())));
290
-                    $xPrevious->appendChild($DOM->createElement('s:code', $h($previous->getCode())));
291
-                    $xPrevious->appendChild($DOM->createElement('s:stacktrace', $h($previous->getTraceAsString())));
292
-                    $error->appendChild($xPrevious);
293
-                }
294
-            }
295
-
296
-
297
-            if ($e instanceof Exception) {
298
-
299
-                $httpCode = $e->getHTTPCode();
300
-                $e->serialize($this, $error);
301
-                $headers = $e->getHTTPHeaders($this);
302
-
303
-            } else {
304
-
305
-                $httpCode = 500;
306
-                $headers = [];
307
-
308
-            }
309
-            $headers['Content-Type'] = 'application/xml; charset=utf-8';
310
-
311
-            $this->httpResponse->setStatus($httpCode);
312
-            $this->httpResponse->setHeaders($headers);
313
-            $this->httpResponse->setBody($DOM->saveXML());
314
-            $this->sapi->sendResponse($this->httpResponse);
315
-
316
-        }
317
-
318
-    }
319
-
320
-    /**
321
-     * Sets the base server uri
322
-     *
323
-     * @param string $uri
324
-     * @return void
325
-     */
326
-    public function setBaseUri($uri) {
327
-
328
-        // If the baseUri does not end with a slash, we must add it
329
-        if ($uri[strlen($uri) - 1] !== '/')
330
-            $uri .= '/';
331
-
332
-        $this->baseUri = $uri;
333
-
334
-    }
335
-
336
-    /**
337
-     * Returns the base responding uri
338
-     *
339
-     * @return string
340
-     */
341
-    public function getBaseUri() {
342
-
343
-        if (is_null($this->baseUri)) $this->baseUri = $this->guessBaseUri();
344
-        return $this->baseUri;
345
-
346
-    }
347
-
348
-    /**
349
-     * This method attempts to detect the base uri.
350
-     * Only the PATH_INFO variable is considered.
351
-     *
352
-     * If this variable is not set, the root (/) is assumed.
353
-     *
354
-     * @return string
355
-     */
356
-    public function guessBaseUri() {
357
-
358
-        $pathInfo = $this->httpRequest->getRawServerValue('PATH_INFO');
359
-        $uri = $this->httpRequest->getRawServerValue('REQUEST_URI');
360
-
361
-        // If PATH_INFO is found, we can assume it's accurate.
362
-        if (!empty($pathInfo)) {
363
-
364
-            // We need to make sure we ignore the QUERY_STRING part
365
-            if ($pos = strpos($uri, '?'))
366
-                $uri = substr($uri, 0, $pos);
367
-
368
-            // PATH_INFO is only set for urls, such as: /example.php/path
369
-            // in that case PATH_INFO contains '/path'.
370
-            // Note that REQUEST_URI is percent encoded, while PATH_INFO is
371
-            // not, Therefore they are only comparable if we first decode
372
-            // REQUEST_INFO as well.
373
-            $decodedUri = URLUtil::decodePath($uri);
374
-
375
-            // A simple sanity check:
376
-            if (substr($decodedUri, strlen($decodedUri) - strlen($pathInfo)) === $pathInfo) {
377
-                $baseUri = substr($decodedUri, 0, strlen($decodedUri) - strlen($pathInfo));
378
-                return rtrim($baseUri, '/') . '/';
379
-            }
380
-
381
-            throw new Exception('The REQUEST_URI (' . $uri . ') did not end with the contents of PATH_INFO (' . $pathInfo . '). This server might be misconfigured.');
382
-
383
-        }
384
-
385
-        // The last fallback is that we're just going to assume the server root.
386
-        return '/';
387
-
388
-    }
389
-
390
-    /**
391
-     * Adds a plugin to the server
392
-     *
393
-     * For more information, console the documentation of Sabre\DAV\ServerPlugin
394
-     *
395
-     * @param ServerPlugin $plugin
396
-     * @return void
397
-     */
398
-    public function addPlugin(ServerPlugin $plugin) {
399
-
400
-        $this->plugins[$plugin->getPluginName()] = $plugin;
401
-        $plugin->initialize($this);
402
-
403
-    }
404
-
405
-    /**
406
-     * Returns an initialized plugin by it's name.
407
-     *
408
-     * This function returns null if the plugin was not found.
409
-     *
410
-     * @param string $name
411
-     * @return ServerPlugin
412
-     */
413
-    public function getPlugin($name) {
414
-
415
-        if (isset($this->plugins[$name]))
416
-            return $this->plugins[$name];
417
-
418
-        return null;
419
-
420
-    }
421
-
422
-    /**
423
-     * Returns all plugins
424
-     *
425
-     * @return array
426
-     */
427
-    public function getPlugins() {
428
-
429
-        return $this->plugins;
430
-
431
-    }
432
-
433
-    /**
434
-     * Handles a http request, and execute a method based on its name
435
-     *
436
-     * @param RequestInterface $request
437
-     * @param ResponseInterface $response
438
-     * @param $sendResponse Whether to send the HTTP response to the DAV client.
439
-     * @return void
440
-     */
441
-    public function invokeMethod(RequestInterface $request, ResponseInterface $response, $sendResponse = true) {
442
-
443
-        $method = $request->getMethod();
444
-
445
-        if (!$this->emit('beforeMethod:' . $method, [$request, $response])) return;
446
-        if (!$this->emit('beforeMethod', [$request, $response])) return;
447
-
448
-        if (self::$exposeVersion) {
449
-            $response->setHeader('X-Sabre-Version', Version::VERSION);
450
-        }
451
-
452
-        $this->transactionType = strtolower($method);
453
-
454
-        if (!$this->checkPreconditions($request, $response)) {
455
-            $this->sapi->sendResponse($response);
456
-            return;
457
-        }
458
-
459
-        if ($this->emit('method:' . $method, [$request, $response])) {
460
-            if ($this->emit('method', [$request, $response])) {
461
-                $exMessage = "There was no plugin in the system that was willing to handle this " . $method . " method.";
462
-                if ($method === "GET") {
463
-                    $exMessage .= " Enable the Browser plugin to get a better result here.";
464
-                }
465
-
466
-                // Unsupported method
467
-                throw new Exception\NotImplemented($exMessage);
468
-            }
469
-        }
470
-
471
-        if (!$this->emit('afterMethod:' . $method, [$request, $response])) return;
472
-        if (!$this->emit('afterMethod', [$request, $response])) return;
21
+	/**
22
+	 * Infinity is used for some request supporting the HTTP Depth header and indicates that the operation should traverse the entire tree
23
+	 */
24
+	const DEPTH_INFINITY = -1;
25
+
26
+	/**
27
+	 * XML namespace for all SabreDAV related elements
28
+	 */
29
+	const NS_SABREDAV = 'http://sabredav.org/ns';
30
+
31
+	/**
32
+	 * The tree object
33
+	 *
34
+	 * @var Sabre\DAV\Tree
35
+	 */
36
+	public $tree;
37
+
38
+	/**
39
+	 * The base uri
40
+	 *
41
+	 * @var string
42
+	 */
43
+	protected $baseUri = null;
44
+
45
+	/**
46
+	 * httpResponse
47
+	 *
48
+	 * @var Sabre\HTTP\Response
49
+	 */
50
+	public $httpResponse;
51
+
52
+	/**
53
+	 * httpRequest
54
+	 *
55
+	 * @var Sabre\HTTP\Request
56
+	 */
57
+	public $httpRequest;
58
+
59
+	/**
60
+	 * PHP HTTP Sapi
61
+	 *
62
+	 * @var Sabre\HTTP\Sapi
63
+	 */
64
+	public $sapi;
65
+
66
+	/**
67
+	 * The list of plugins
68
+	 *
69
+	 * @var array
70
+	 */
71
+	protected $plugins = [];
72
+
73
+	/**
74
+	 * This property will be filled with a unique string that describes the
75
+	 * transaction. This is useful for performance measuring and logging
76
+	 * purposes.
77
+	 *
78
+	 * By default it will just fill it with a lowercased HTTP method name, but
79
+	 * plugins override this. For example, the WebDAV-Sync sync-collection
80
+	 * report will set this to 'report-sync-collection'.
81
+	 *
82
+	 * @var string
83
+	 */
84
+	public $transactionType;
85
+
86
+	/**
87
+	 * This is a list of properties that are always server-controlled, and
88
+	 * must not get modified with PROPPATCH.
89
+	 *
90
+	 * Plugins may add to this list.
91
+	 *
92
+	 * @var string[]
93
+	 */
94
+	public $protectedProperties = [
95
+
96
+		// RFC4918
97
+		'{DAV:}getcontentlength',
98
+		'{DAV:}getetag',
99
+		'{DAV:}getlastmodified',
100
+		'{DAV:}lockdiscovery',
101
+		'{DAV:}supportedlock',
102
+
103
+		// RFC4331
104
+		'{DAV:}quota-available-bytes',
105
+		'{DAV:}quota-used-bytes',
106
+
107
+		// RFC3744
108
+		'{DAV:}supported-privilege-set',
109
+		'{DAV:}current-user-privilege-set',
110
+		'{DAV:}acl',
111
+		'{DAV:}acl-restrictions',
112
+		'{DAV:}inherited-acl-set',
113
+
114
+		// RFC3253
115
+		'{DAV:}supported-method-set',
116
+		'{DAV:}supported-report-set',
117
+
118
+		// RFC6578
119
+		'{DAV:}sync-token',
120
+
121
+		// calendarserver.org extensions
122
+		'{http://calendarserver.org/ns/}ctag',
123
+
124
+		// sabredav extensions
125
+		'{http://sabredav.org/ns}sync-token',
126
+
127
+	];
128
+
129
+	/**
130
+	 * This is a flag that allow or not showing file, line and code
131
+	 * of the exception in the returned XML
132
+	 *
133
+	 * @var bool
134
+	 */
135
+	public $debugExceptions = false;
136
+
137
+	/**
138
+	 * This property allows you to automatically add the 'resourcetype' value
139
+	 * based on a node's classname or interface.
140
+	 *
141
+	 * The preset ensures that {DAV:}collection is automatically added for nodes
142
+	 * implementing Sabre\DAV\ICollection.
143
+	 *
144
+	 * @var array
145
+	 */
146
+	public $resourceTypeMapping = [
147
+		'Sabre\\DAV\\ICollection' => '{DAV:}collection',
148
+	];
149
+
150
+	/**
151
+	 * This property allows the usage of Depth: infinity on PROPFIND requests.
152
+	 *
153
+	 * By default Depth: infinity is treated as Depth: 1. Allowing Depth:
154
+	 * infinity is potentially risky, as it allows a single client to do a full
155
+	 * index of the webdav server, which is an easy DoS attack vector.
156
+	 *
157
+	 * Only turn this on if you know what you're doing.
158
+	 *
159
+	 * @var bool
160
+	 */
161
+	public $enablePropfindDepthInfinity = false;
162
+
163
+	/**
164
+	 * Reference to the XML utility object.
165
+	 *
166
+	 * @var Xml\Service
167
+	 */
168
+	public $xml;
169
+
170
+	/**
171
+	 * If this setting is turned off, SabreDAV's version number will be hidden
172
+	 * from various places.
173
+	 *
174
+	 * Some people feel this is a good security measure.
175
+	 *
176
+	 * @var bool
177
+	 */
178
+	static $exposeVersion = true;
179
+
180
+	/**
181
+	 * Sets up the server
182
+	 *
183
+	 * If a Sabre\DAV\Tree object is passed as an argument, it will
184
+	 * use it as the directory tree. If a Sabre\DAV\INode is passed, it
185
+	 * will create a Sabre\DAV\Tree and use the node as the root.
186
+	 *
187
+	 * If nothing is passed, a Sabre\DAV\SimpleCollection is created in
188
+	 * a Sabre\DAV\Tree.
189
+	 *
190
+	 * If an array is passed, we automatically create a root node, and use
191
+	 * the nodes in the array as top-level children.
192
+	 *
193
+	 * @param Tree|INode|array|null $treeOrNode The tree object
194
+	 */
195
+	public function __construct($treeOrNode = null) {
196
+
197
+		if ($treeOrNode instanceof Tree) {
198
+			$this->tree = $treeOrNode;
199
+		} elseif ($treeOrNode instanceof INode) {
200
+			$this->tree = new Tree($treeOrNode);
201
+		} elseif (is_array($treeOrNode)) {
202
+
203
+			// If it's an array, a list of nodes was passed, and we need to
204
+			// create the root node.
205
+			foreach ($treeOrNode as $node) {
206
+				if (!($node instanceof INode)) {
207
+					throw new Exception('Invalid argument passed to constructor. If you\'re passing an array, all the values must implement Sabre\\DAV\\INode');
208
+				}
209
+			}
210
+
211
+			$root = new SimpleCollection('root', $treeOrNode);
212
+			$this->tree = new Tree($root);
213
+
214
+		} elseif (is_null($treeOrNode)) {
215
+			$root = new SimpleCollection('root');
216
+			$this->tree = new Tree($root);
217
+		} else {
218
+			throw new Exception('Invalid argument passed to constructor. Argument must either be an instance of Sabre\\DAV\\Tree, Sabre\\DAV\\INode, an array or null');
219
+		}
220
+
221
+		$this->xml = new Xml\Service();
222
+		$this->sapi = new HTTP\Sapi();
223
+		$this->httpResponse = new HTTP\Response();
224
+		$this->httpRequest = $this->sapi->getRequest();
225
+		$this->addPlugin(new CorePlugin());
226
+
227
+	}
228
+
229
+	/**
230
+	 * Starts the DAV Server
231
+	 *
232
+	 * @return void
233
+	 */
234
+	public function exec() {
235
+
236
+		try {
237
+
238
+			// If nginx (pre-1.2) is used as a proxy server, and SabreDAV as an
239
+			// origin, we must make sure we send back HTTP/1.0 if this was
240
+			// requested.
241
+			// This is mainly because nginx doesn't support Chunked Transfer
242
+			// Encoding, and this forces the webserver SabreDAV is running on,
243
+			// to buffer entire responses to calculate Content-Length.
244
+			$this->httpResponse->setHTTPVersion($this->httpRequest->getHTTPVersion());
245
+
246
+			// Setting the base url
247
+			$this->httpRequest->setBaseUrl($this->getBaseUri());
248
+			$this->invokeMethod($this->httpRequest, $this->httpResponse);
249
+
250
+		} catch (\Exception $e) {
251
+
252
+			try {
253
+				$this->emit('exception', [$e]);
254
+			} catch (\Exception $ignore) {
255
+			}
256
+			$DOM = new \DOMDocument('1.0', 'utf-8');
257
+			$DOM->formatOutput = true;
258
+
259
+			$error = $DOM->createElementNS('DAV:', 'd:error');
260
+			$error->setAttribute('xmlns:s', self::NS_SABREDAV);
261
+			$DOM->appendChild($error);
262
+
263
+			$h = function($v) {
264
+
265
+				return htmlspecialchars($v, ENT_NOQUOTES, 'UTF-8');
266
+
267
+			};
268
+
269
+			if (self::$exposeVersion) {
270
+				$error->appendChild($DOM->createElement('s:sabredav-version', $h(Version::VERSION)));
271
+			}
272
+
273
+			$error->appendChild($DOM->createElement('s:exception', $h(get_class($e))));
274
+			$error->appendChild($DOM->createElement('s:message', $h($e->getMessage())));
275
+			if ($this->debugExceptions) {
276
+				$error->appendChild($DOM->createElement('s:file', $h($e->getFile())));
277
+				$error->appendChild($DOM->createElement('s:line', $h($e->getLine())));
278
+				$error->appendChild($DOM->createElement('s:code', $h($e->getCode())));
279
+				$error->appendChild($DOM->createElement('s:stacktrace', $h($e->getTraceAsString())));
280
+			}
281
+
282
+			if ($this->debugExceptions) {
283
+				$previous = $e;
284
+				while ($previous = $previous->getPrevious()) {
285
+					$xPrevious = $DOM->createElement('s:previous-exception');
286
+					$xPrevious->appendChild($DOM->createElement('s:exception', $h(get_class($previous))));
287
+					$xPrevious->appendChild($DOM->createElement('s:message', $h($previous->getMessage())));
288
+					$xPrevious->appendChild($DOM->createElement('s:file', $h($previous->getFile())));
289
+					$xPrevious->appendChild($DOM->createElement('s:line', $h($previous->getLine())));
290
+					$xPrevious->appendChild($DOM->createElement('s:code', $h($previous->getCode())));
291
+					$xPrevious->appendChild($DOM->createElement('s:stacktrace', $h($previous->getTraceAsString())));
292
+					$error->appendChild($xPrevious);
293
+				}
294
+			}
295
+
296
+
297
+			if ($e instanceof Exception) {
298
+
299
+				$httpCode = $e->getHTTPCode();
300
+				$e->serialize($this, $error);
301
+				$headers = $e->getHTTPHeaders($this);
302
+
303
+			} else {
304
+
305
+				$httpCode = 500;
306
+				$headers = [];
307
+
308
+			}
309
+			$headers['Content-Type'] = 'application/xml; charset=utf-8';
310
+
311
+			$this->httpResponse->setStatus($httpCode);
312
+			$this->httpResponse->setHeaders($headers);
313
+			$this->httpResponse->setBody($DOM->saveXML());
314
+			$this->sapi->sendResponse($this->httpResponse);
315
+
316
+		}
317
+
318
+	}
319
+
320
+	/**
321
+	 * Sets the base server uri
322
+	 *
323
+	 * @param string $uri
324
+	 * @return void
325
+	 */
326
+	public function setBaseUri($uri) {
327
+
328
+		// If the baseUri does not end with a slash, we must add it
329
+		if ($uri[strlen($uri) - 1] !== '/')
330
+			$uri .= '/';
331
+
332
+		$this->baseUri = $uri;
333
+
334
+	}
335
+
336
+	/**
337
+	 * Returns the base responding uri
338
+	 *
339
+	 * @return string
340
+	 */
341
+	public function getBaseUri() {
342
+
343
+		if (is_null($this->baseUri)) $this->baseUri = $this->guessBaseUri();
344
+		return $this->baseUri;
345
+
346
+	}
347
+
348
+	/**
349
+	 * This method attempts to detect the base uri.
350
+	 * Only the PATH_INFO variable is considered.
351
+	 *
352
+	 * If this variable is not set, the root (/) is assumed.
353
+	 *
354
+	 * @return string
355
+	 */
356
+	public function guessBaseUri() {
357
+
358
+		$pathInfo = $this->httpRequest->getRawServerValue('PATH_INFO');
359
+		$uri = $this->httpRequest->getRawServerValue('REQUEST_URI');
360
+
361
+		// If PATH_INFO is found, we can assume it's accurate.
362
+		if (!empty($pathInfo)) {
363
+
364
+			// We need to make sure we ignore the QUERY_STRING part
365
+			if ($pos = strpos($uri, '?'))
366
+				$uri = substr($uri, 0, $pos);
367
+
368
+			// PATH_INFO is only set for urls, such as: /example.php/path
369
+			// in that case PATH_INFO contains '/path'.
370
+			// Note that REQUEST_URI is percent encoded, while PATH_INFO is
371
+			// not, Therefore they are only comparable if we first decode
372
+			// REQUEST_INFO as well.
373
+			$decodedUri = URLUtil::decodePath($uri);
374
+
375
+			// A simple sanity check:
376
+			if (substr($decodedUri, strlen($decodedUri) - strlen($pathInfo)) === $pathInfo) {
377
+				$baseUri = substr($decodedUri, 0, strlen($decodedUri) - strlen($pathInfo));
378
+				return rtrim($baseUri, '/') . '/';
379
+			}
380
+
381
+			throw new Exception('The REQUEST_URI (' . $uri . ') did not end with the contents of PATH_INFO (' . $pathInfo . '). This server might be misconfigured.');
382
+
383
+		}
384
+
385
+		// The last fallback is that we're just going to assume the server root.
386
+		return '/';
387
+
388
+	}
389
+
390
+	/**
391
+	 * Adds a plugin to the server
392
+	 *
393
+	 * For more information, console the documentation of Sabre\DAV\ServerPlugin
394
+	 *
395
+	 * @param ServerPlugin $plugin
396
+	 * @return void
397
+	 */
398
+	public function addPlugin(ServerPlugin $plugin) {
399
+
400
+		$this->plugins[$plugin->getPluginName()] = $plugin;
401
+		$plugin->initialize($this);
402
+
403
+	}
404
+
405
+	/**
406
+	 * Returns an initialized plugin by it's name.
407
+	 *
408
+	 * This function returns null if the plugin was not found.
409
+	 *
410
+	 * @param string $name
411
+	 * @return ServerPlugin
412
+	 */
413
+	public function getPlugin($name) {
414
+
415
+		if (isset($this->plugins[$name]))
416
+			return $this->plugins[$name];
417
+
418
+		return null;
419
+
420
+	}
421
+
422
+	/**
423
+	 * Returns all plugins
424
+	 *
425
+	 * @return array
426
+	 */
427
+	public function getPlugins() {
428
+
429
+		return $this->plugins;
430
+
431
+	}
432
+
433
+	/**
434
+	 * Handles a http request, and execute a method based on its name
435
+	 *
436
+	 * @param RequestInterface $request
437
+	 * @param ResponseInterface $response
438
+	 * @param $sendResponse Whether to send the HTTP response to the DAV client.
439
+	 * @return void
440
+	 */
441
+	public function invokeMethod(RequestInterface $request, ResponseInterface $response, $sendResponse = true) {
442
+
443
+		$method = $request->getMethod();
444
+
445
+		if (!$this->emit('beforeMethod:' . $method, [$request, $response])) return;
446
+		if (!$this->emit('beforeMethod', [$request, $response])) return;
447
+
448
+		if (self::$exposeVersion) {
449
+			$response->setHeader('X-Sabre-Version', Version::VERSION);
450
+		}
451
+
452
+		$this->transactionType = strtolower($method);
453
+
454
+		if (!$this->checkPreconditions($request, $response)) {
455
+			$this->sapi->sendResponse($response);
456
+			return;
457
+		}
458
+
459
+		if ($this->emit('method:' . $method, [$request, $response])) {
460
+			if ($this->emit('method', [$request, $response])) {
461
+				$exMessage = "There was no plugin in the system that was willing to handle this " . $method . " method.";
462
+				if ($method === "GET") {
463
+					$exMessage .= " Enable the Browser plugin to get a better result here.";
464
+				}
465
+
466
+				// Unsupported method
467
+				throw new Exception\NotImplemented($exMessage);
468
+			}
469
+		}
470
+
471
+		if (!$this->emit('afterMethod:' . $method, [$request, $response])) return;
472
+		if (!$this->emit('afterMethod', [$request, $response])) return;
473 473
 
474
-        if ($response->getStatus() === null) {
475
-            throw new Exception('No subsystem set a valid HTTP status code. Something must have interrupted the request without providing further detail.');
476
-        }
477
-        if ($sendResponse) {
478
-            $this->sapi->sendResponse($response);
479
-            $this->emit('afterResponse', [$request, $response]);
480
-        }
481
-
482
-    }
483
-
484
-    // {{{ HTTP/WebDAV protocol helpers
485
-
486
-    /**
487
-     * Returns an array with all the supported HTTP methods for a specific uri.
488
-     *
489
-     * @param string $path
490
-     * @return array
491
-     */
492
-    public function getAllowedMethods($path) {
493
-
494
-        $methods = [
495
-            'OPTIONS',
496
-            'GET',
497
-            'HEAD',
498
-            'DELETE',
499
-            'PROPFIND',
500
-            'PUT',
501
-            'PROPPATCH',
502
-            'COPY',
503
-            'MOVE',
504
-            'REPORT'
505
-        ];
506
-
507
-        // The MKCOL is only allowed on an unmapped uri
508
-        try {
509
-            $this->tree->getNodeForPath($path);
510
-        } catch (Exception\NotFound $e) {
511
-            $methods[] = 'MKCOL';
512
-        }
513
-
514
-        // We're also checking if any of the plugins register any new methods
515
-        foreach ($this->plugins as $plugin) $methods = array_merge($methods, $plugin->getHTTPMethods($path));
516
-        array_unique($methods);
517
-
518
-        return $methods;
519
-
520
-    }
521
-
522
-    /**
523
-     * Gets the uri for the request, keeping the base uri into consideration
524
-     *
525
-     * @return string
526
-     */
527
-    public function getRequestUri() {
474
+		if ($response->getStatus() === null) {
475
+			throw new Exception('No subsystem set a valid HTTP status code. Something must have interrupted the request without providing further detail.');
476
+		}
477
+		if ($sendResponse) {
478
+			$this->sapi->sendResponse($response);
479
+			$this->emit('afterResponse', [$request, $response]);
480
+		}
481
+
482
+	}
483
+
484
+	// {{{ HTTP/WebDAV protocol helpers
485
+
486
+	/**
487
+	 * Returns an array with all the supported HTTP methods for a specific uri.
488
+	 *
489
+	 * @param string $path
490
+	 * @return array
491
+	 */
492
+	public function getAllowedMethods($path) {
493
+
494
+		$methods = [
495
+			'OPTIONS',
496
+			'GET',
497
+			'HEAD',
498
+			'DELETE',
499
+			'PROPFIND',
500
+			'PUT',
501
+			'PROPPATCH',
502
+			'COPY',
503
+			'MOVE',
504
+			'REPORT'
505
+		];
506
+
507
+		// The MKCOL is only allowed on an unmapped uri
508
+		try {
509
+			$this->tree->getNodeForPath($path);
510
+		} catch (Exception\NotFound $e) {
511
+			$methods[] = 'MKCOL';
512
+		}
513
+
514
+		// We're also checking if any of the plugins register any new methods
515
+		foreach ($this->plugins as $plugin) $methods = array_merge($methods, $plugin->getHTTPMethods($path));
516
+		array_unique($methods);
517
+
518
+		return $methods;
519
+
520
+	}
521
+
522
+	/**
523
+	 * Gets the uri for the request, keeping the base uri into consideration
524
+	 *
525
+	 * @return string
526
+	 */
527
+	public function getRequestUri() {
528 528
 
529
-        return $this->calculateUri($this->httpRequest->getUrl());
529
+		return $this->calculateUri($this->httpRequest->getUrl());
530 530
 
531
-    }
531
+	}
532 532
 
533
-    /**
534
-     * Turns a URI such as the REQUEST_URI into a local path.
535
-     *
536
-     * This method:
537
-     *   * strips off the base path
538
-     *   * normalizes the path
539
-     *   * uri-decodes the path
540
-     *
541
-     * @param string $uri
542
-     * @throws Exception\Forbidden A permission denied exception is thrown whenever there was an attempt to supply a uri outside of the base uri
543
-     * @return string
544
-     */
545
-    public function calculateUri($uri) {
546
-
547
-        if ($uri[0] != '/' && strpos($uri, '://')) {
548
-
549
-            $uri = parse_url($uri, PHP_URL_PATH);
550
-
551
-        }
552
-
553
-        $uri = Uri\normalize(str_replace('//', '/', $uri));
554
-        $baseUri = Uri\normalize($this->getBaseUri());
555
-
556
-        if (strpos($uri, $baseUri) === 0) {
557
-
558
-            return trim(URLUtil::decodePath(substr($uri, strlen($baseUri))), '/');
559
-
560
-        // A special case, if the baseUri was accessed without a trailing
561
-        // slash, we'll accept it as well.
562
-        } elseif ($uri . '/' === $baseUri) {
563
-
564
-            return '';
565
-
566
-        } else {
567
-
568
-            throw new Exception\Forbidden('Requested uri (' . $uri . ') is out of base uri (' . $this->getBaseUri() . ')');
569
-
570
-        }
571
-
572
-    }
573
-
574
-    /**
575
-     * Returns the HTTP depth header
576
-     *
577
-     * This method returns the contents of the HTTP depth request header. If the depth header was 'infinity' it will return the Sabre\DAV\Server::DEPTH_INFINITY object
578
-     * It is possible to supply a default depth value, which is used when the depth header has invalid content, or is completely non-existent
579
-     *
580
-     * @param mixed $default
581
-     * @return int
582
-     */
583
-    public function getHTTPDepth($default = self::DEPTH_INFINITY) {
584
-
585
-        // If its not set, we'll grab the default
586
-        $depth = $this->httpRequest->getHeader('Depth');
587
-
588
-        if (is_null($depth)) return $default;
589
-
590
-        if ($depth == 'infinity') return self::DEPTH_INFINITY;
591
-
592
-
593
-        // If its an unknown value. we'll grab the default
594
-        if (!ctype_digit($depth)) return $default;
595
-
596
-        return (int)$depth;
597
-
598
-    }
599
-
600
-    /**
601
-     * Returns the HTTP range header
602
-     *
603
-     * This method returns null if there is no well-formed HTTP range request
604
-     * header or array($start, $end).
605
-     *
606
-     * The first number is the offset of the first byte in the range.
607
-     * The second number is the offset of the last byte in the range.
608
-     *
609
-     * If the second offset is null, it should be treated as the offset of the last byte of the entity
610
-     * If the first offset is null, the second offset should be used to retrieve the last x bytes of the entity
611
-     *
612
-     * @return array|null
613
-     */
614
-    public function getHTTPRange() {
615
-
616
-        $range = $this->httpRequest->getHeader('range');
617
-        if (is_null($range)) return null;
618
-
619
-        // Matching "Range: bytes=1234-5678: both numbers are optional
620
-
621
-        if (!preg_match('/^bytes=([0-9]*)-([0-9]*)$/i', $range, $matches)) return null;
622
-
623
-        if ($matches[1] === '' && $matches[2] === '') return null;
624
-
625
-        return [
626
-            $matches[1] !== '' ? $matches[1] : null,
627
-            $matches[2] !== '' ? $matches[2] : null,
628
-        ];
629
-
630
-    }
631
-
632
-    /**
633
-     * Returns the HTTP Prefer header information.
634
-     *
635
-     * The prefer header is defined in:
636
-     * http://tools.ietf.org/html/draft-snell-http-prefer-14
637
-     *
638
-     * This method will return an array with options.
639
-     *
640
-     * Currently, the following options may be returned:
641
-     *  [
642
-     *      'return-asynch'         => true,
643
-     *      'return-minimal'        => true,
644
-     *      'return-representation' => true,
645
-     *      'wait'                  => 30,
646
-     *      'strict'                => true,
647
-     *      'lenient'               => true,
648
-     *  ]
649
-     *
650
-     * This method also supports the Brief header, and will also return
651
-     * 'return-minimal' if the brief header was set to 't'.
652
-     *
653
-     * For the boolean options, false will be returned if the headers are not
654
-     * specified. For the integer options it will be 'null'.
655
-     *
656
-     * @return array
657
-     */
658
-    public function getHTTPPrefer() {
659
-
660
-        $result = [
661
-            // can be true or false
662
-            'respond-async' => false,
663
-            // Could be set to 'representation' or 'minimal'.
664
-            'return'        => null,
665
-            // Used as a timeout, is usually a number.
666
-            'wait'          => null,
667
-            // can be 'strict' or 'lenient'.
668
-            'handling'      => false,
669
-        ];
670
-
671
-        if ($prefer = $this->httpRequest->getHeader('Prefer')) {
672
-
673
-            $result = array_merge(
674
-                $result,
675
-                \Sabre\HTTP\parsePrefer($prefer)
676
-            );
677
-
678
-        } elseif ($this->httpRequest->getHeader('Brief') == 't') {
679
-            $result['return'] = 'minimal';
680
-        }
681
-
682
-        return $result;
683
-
684
-    }
685
-
686
-
687
-    /**
688
-     * Returns information about Copy and Move requests
689
-     *
690
-     * This function is created to help getting information about the source and the destination for the
691
-     * WebDAV MOVE and COPY HTTP request. It also validates a lot of information and throws proper exceptions
692
-     *
693
-     * The returned value is an array with the following keys:
694
-     *   * destination - Destination path
695
-     *   * destinationExists - Whether or not the destination is an existing url (and should therefore be overwritten)
696
-     *
697
-     * @param RequestInterface $request
698
-     * @throws Exception\BadRequest upon missing or broken request headers
699
-     * @throws Exception\UnsupportedMediaType when trying to copy into a
700
-     *         non-collection.
701
-     * @throws Exception\PreconditionFailed If overwrite is set to false, but
702
-     *         the destination exists.
703
-     * @throws Exception\Forbidden when source and destination paths are
704
-     *         identical.
705
-     * @throws Exception\Conflict When trying to copy a node into its own
706
-     *         subtree.
707
-     * @return array
708
-     */
709
-    public function getCopyAndMoveInfo(RequestInterface $request) {
710
-
711
-        // Collecting the relevant HTTP headers
712
-        if (!$request->getHeader('Destination')) throw new Exception\BadRequest('The destination header was not supplied');
713
-        $destination = $this->calculateUri($request->getHeader('Destination'));
714
-        $overwrite = $request->getHeader('Overwrite');
715
-        if (!$overwrite) $overwrite = 'T';
716
-        if (strtoupper($overwrite) == 'T') $overwrite = true;
717
-        elseif (strtoupper($overwrite) == 'F') $overwrite = false;
718
-        // We need to throw a bad request exception, if the header was invalid
719
-        else throw new Exception\BadRequest('The HTTP Overwrite header should be either T or F');
720
-
721
-        list($destinationDir) = URLUtil::splitPath($destination);
722
-
723
-        try {
724
-            $destinationParent = $this->tree->getNodeForPath($destinationDir);
725
-            if (!($destinationParent instanceof ICollection)) throw new Exception\UnsupportedMediaType('The destination node is not a collection');
726
-        } catch (Exception\NotFound $e) {
727
-
728
-            // If the destination parent node is not found, we throw a 409
729
-            throw new Exception\Conflict('The destination node is not found');
730
-        }
731
-
732
-        try {
733
-
734
-            $destinationNode = $this->tree->getNodeForPath($destination);
735
-
736
-            // If this succeeded, it means the destination already exists
737
-            // we'll need to throw precondition failed in case overwrite is false
738
-            if (!$overwrite) throw new Exception\PreconditionFailed('The destination node already exists, and the overwrite header is set to false', 'Overwrite');
739
-
740
-        } catch (Exception\NotFound $e) {
741
-
742
-            // Destination didn't exist, we're all good
743
-            $destinationNode = false;
744
-
745
-        }
746
-
747
-        $requestPath = $request->getPath();
748
-        if ($destination === $requestPath) {
749
-            throw new Exception\Forbidden('Source and destination uri are identical.');
750
-        }
751
-        if (substr($destination, 0, strlen($requestPath) + 1) === $requestPath . '/') {
752
-            throw new Exception\Conflict('The destination may not be part of the same subtree as the source path.');
753
-        }
754
-
755
-        // These are the three relevant properties we need to return
756
-        return [
757
-            'destination'       => $destination,
758
-            'destinationExists' => !!$destinationNode,
759
-            'destinationNode'   => $destinationNode,
760
-        ];
761
-
762
-    }
763
-
764
-    /**
765
-     * Returns a list of properties for a path
766
-     *
767
-     * This is a simplified version getPropertiesForPath. If you aren't
768
-     * interested in status codes, but you just want to have a flat list of
769
-     * properties, use this method.
770
-     *
771
-     * Please note though that any problems related to retrieving properties,
772
-     * such as permission issues will just result in an empty array being
773
-     * returned.
774
-     *
775
-     * @param string $path
776
-     * @param array $propertyNames
777
-     */
778
-    public function getProperties($path, $propertyNames) {
779
-
780
-        $result = $this->getPropertiesForPath($path, $propertyNames, 0);
781
-        if (isset($result[0][200])) {
782
-            return $result[0][200];
783
-        } else {
784
-            return [];
785
-        }
786
-
787
-    }
788
-
789
-    /**
790
-     * A kid-friendly way to fetch properties for a node's children.
791
-     *
792
-     * The returned array will be indexed by the path of the of child node.
793
-     * Only properties that are actually found will be returned.
794
-     *
795
-     * The parent node will not be returned.
796
-     *
797
-     * @param string $path
798
-     * @param array $propertyNames
799
-     * @return array
800
-     */
801
-    public function getPropertiesForChildren($path, $propertyNames) {
802
-
803
-        $result = [];
804
-        foreach ($this->getPropertiesForPath($path, $propertyNames, 1) as $k => $row) {
805
-
806
-            // Skipping the parent path
807
-            if ($k === 0) continue;
808
-
809
-            $result[$row['href']] = $row[200];
810
-
811
-        }
812
-        return $result;
813
-
814
-    }
815
-
816
-    /**
817
-     * Returns a list of HTTP headers for a particular resource
818
-     *
819
-     * The generated http headers are based on properties provided by the
820
-     * resource. The method basically provides a simple mapping between
821
-     * DAV property and HTTP header.
822
-     *
823
-     * The headers are intended to be used for HEAD and GET requests.
824
-     *
825
-     * @param string $path
826
-     * @return array
827
-     */
828
-    public function getHTTPHeaders($path) {
829
-
830
-        $propertyMap = [
831
-            '{DAV:}getcontenttype'   => 'Content-Type',
832
-            '{DAV:}getcontentlength' => 'Content-Length',
833
-            '{DAV:}getlastmodified'  => 'Last-Modified',
834
-            '{DAV:}getetag'          => 'ETag',
835
-        ];
836
-
837
-        $properties = $this->getProperties($path, array_keys($propertyMap));
838
-
839
-        $headers = [];
840
-        foreach ($propertyMap as $property => $header) {
841
-            if (!isset($properties[$property])) continue;
842
-
843
-            if (is_scalar($properties[$property])) {
844
-                $headers[$header] = $properties[$property];
845
-
846
-            // GetLastModified gets special cased
847
-            } elseif ($properties[$property] instanceof Xml\Property\GetLastModified) {
848
-                $headers[$header] = HTTP\Util::toHTTPDate($properties[$property]->getTime());
849
-            }
850
-
851
-        }
852
-
853
-        return $headers;
854
-
855
-    }
856
-
857
-    /**
858
-     * Small helper to support PROPFIND with DEPTH_INFINITY.
859
-     *
860
-     * @param array[] $propFindRequests
861
-     * @param PropFind $propFind
862
-     * @return void
863
-     */
864
-    private function addPathNodesRecursively(&$propFindRequests, PropFind $propFind) {
865
-
866
-        $newDepth = $propFind->getDepth();
867
-        $path = $propFind->getPath();
868
-
869
-        if ($newDepth !== self::DEPTH_INFINITY) {
870
-            $newDepth--;
871
-        }
872
-
873
-        foreach ($this->tree->getChildren($path) as $childNode) {
874
-            $subPropFind = clone $propFind;
875
-            $subPropFind->setDepth($newDepth);
876
-            if ($path !== '') {
877
-                $subPath = $path . '/' . $childNode->getName();
878
-            } else {
879
-                $subPath = $childNode->getName();
880
-            }
881
-            $subPropFind->setPath($subPath);
882
-
883
-            $propFindRequests[] = [
884
-                $subPropFind,
885
-                $childNode
886
-            ];
887
-
888
-            if (($newDepth === self::DEPTH_INFINITY || $newDepth >= 1) && $childNode instanceof ICollection) {
889
-                $this->addPathNodesRecursively($propFindRequests, $subPropFind);
890
-            }
891
-
892
-        }
893
-    }
894
-
895
-    /**
896
-     * Returns a list of properties for a given path
897
-     *
898
-     * The path that should be supplied should have the baseUrl stripped out
899
-     * The list of properties should be supplied in Clark notation. If the list is empty
900
-     * 'allprops' is assumed.
901
-     *
902
-     * If a depth of 1 is requested child elements will also be returned.
903
-     *
904
-     * @param string $path
905
-     * @param array $propertyNames
906
-     * @param int $depth
907
-     * @return array
908
-     */
909
-    public function getPropertiesForPath($path, $propertyNames = [], $depth = 0) {
910
-
911
-        // The only two options for the depth of a propfind is 0 or 1 - as long as depth infinity is not enabled
912
-        if (!$this->enablePropfindDepthInfinity && $depth != 0) $depth = 1;
913
-
914
-        $path = trim($path, '/');
915
-
916
-        $propFindType = $propertyNames ? PropFind::NORMAL : PropFind::ALLPROPS;
917
-        $propFind = new PropFind($path, (array)$propertyNames, $depth, $propFindType);
918
-
919
-        $parentNode = $this->tree->getNodeForPath($path);
920
-
921
-        $propFindRequests = [[
922
-            $propFind,
923
-            $parentNode
924
-        ]];
925
-
926
-        if (($depth > 0 || $depth === self::DEPTH_INFINITY) && $parentNode instanceof ICollection) {
927
-            $this->addPathNodesRecursively($propFindRequests, $propFind);
928
-        }
929
-
930
-        $returnPropertyList = [];
931
-
932
-        foreach ($propFindRequests as $propFindRequest) {
933
-
934
-            list($propFind, $node) = $propFindRequest;
935
-            $r = $this->getPropertiesByNode($propFind, $node);
936
-            if ($r) {
937
-                $result = $propFind->getResultForMultiStatus();
938
-                $result['href'] = $propFind->getPath();
939
-
940
-                // WebDAV recommends adding a slash to the path, if the path is
941
-                // a collection.
942
-                // Furthermore, iCal also demands this to be the case for
943
-                // principals. This is non-standard, but we support it.
944
-                $resourceType = $this->getResourceTypeForNode($node);
945
-                if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) {
946
-                    $result['href'] .= '/';
947
-                }
948
-                $returnPropertyList[] = $result;
949
-            }
950
-
951
-        }
952
-
953
-        return $returnPropertyList;
954
-
955
-    }
956
-
957
-    /**
958
-     * Returns a list of properties for a list of paths.
959
-     *
960
-     * The path that should be supplied should have the baseUrl stripped out
961
-     * The list of properties should be supplied in Clark notation. If the list is empty
962
-     * 'allprops' is assumed.
963
-     *
964
-     * The result is returned as an array, with paths for it's keys.
965
-     * The result may be returned out of order.
966
-     *
967
-     * @param array $paths
968
-     * @param array $propertyNames
969
-     * @return array
970
-     */
971
-    public function getPropertiesForMultiplePaths(array $paths, array $propertyNames = []) {
972
-
973
-        $result = [
974
-        ];
975
-
976
-        $nodes = $this->tree->getMultipleNodes($paths);
977
-
978
-        foreach ($nodes as $path => $node) {
979
-
980
-            $propFind = new PropFind($path, $propertyNames);
981
-            $r = $this->getPropertiesByNode($propFind, $node);
982
-            if ($r) {
983
-                $result[$path] = $propFind->getResultForMultiStatus();
984
-                $result[$path]['href'] = $path;
985
-
986
-                $resourceType = $this->getResourceTypeForNode($node);
987
-                if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) {
988
-                    $result[$path]['href'] .= '/';
989
-                }
990
-            }
991
-
992
-        }
993
-
994
-        return $result;
995
-
996
-    }
997
-
998
-
999
-    /**
1000
-     * Determines all properties for a node.
1001
-     *
1002
-     * This method tries to grab all properties for a node. This method is used
1003
-     * internally getPropertiesForPath and a few others.
1004
-     *
1005
-     * It could be useful to call this, if you already have an instance of your
1006
-     * target node and simply want to run through the system to get a correct
1007
-     * list of properties.
1008
-     *
1009
-     * @param PropFind $propFind
1010
-     * @param INode $node
1011
-     * @return bool
1012
-     */
1013
-    public function getPropertiesByNode(PropFind $propFind, INode $node) {
1014
-
1015
-        return $this->emit('propFind', [$propFind, $node]);
1016
-
1017
-    }
1018
-
1019
-    /**
1020
-     * This method is invoked by sub-systems creating a new file.
1021
-     *
1022
-     * Currently this is done by HTTP PUT and HTTP LOCK (in the Locks_Plugin).
1023
-     * It was important to get this done through a centralized function,
1024
-     * allowing plugins to intercept this using the beforeCreateFile event.
1025
-     *
1026
-     * This method will return true if the file was actually created
1027
-     *
1028
-     * @param string   $uri
1029
-     * @param resource $data
1030
-     * @param string   $etag
1031
-     * @return bool
1032
-     */
1033
-    public function createFile($uri, $data, &$etag = null) {
1034
-
1035
-        list($dir, $name) = URLUtil::splitPath($uri);
1036
-
1037
-        if (!$this->emit('beforeBind', [$uri])) return false;
1038
-
1039
-        $parent = $this->tree->getNodeForPath($dir);
1040
-        if (!$parent instanceof ICollection) {
1041
-            throw new Exception\Conflict('Files can only be created as children of collections');
1042
-        }
1043
-
1044
-        // It is possible for an event handler to modify the content of the
1045
-        // body, before it gets written. If this is the case, $modified
1046
-        // should be set to true.
1047
-        //
1048
-        // If $modified is true, we must not send back an ETag.
1049
-        $modified = false;
1050
-        if (!$this->emit('beforeCreateFile', [$uri, &$data, $parent, &$modified])) return false;
1051
-
1052
-        $etag = $parent->createFile($name, $data);
1053
-
1054
-        if ($modified) $etag = null;
1055
-
1056
-        $this->tree->markDirty($dir . '/' . $name);
1057
-
1058
-        $this->emit('afterBind', [$uri]);
1059
-        $this->emit('afterCreateFile', [$uri, $parent]);
1060
-
1061
-        return true;
1062
-    }
1063
-
1064
-    /**
1065
-     * This method is invoked by sub-systems updating a file.
1066
-     *
1067
-     * This method will return true if the file was actually updated
1068
-     *
1069
-     * @param string   $uri
1070
-     * @param resource $data
1071
-     * @param string   $etag
1072
-     * @return bool
1073
-     */
1074
-    public function updateFile($uri, $data, &$etag = null) {
1075
-
1076
-        $node = $this->tree->getNodeForPath($uri);
1077
-
1078
-        // It is possible for an event handler to modify the content of the
1079
-        // body, before it gets written. If this is the case, $modified
1080
-        // should be set to true.
1081
-        //
1082
-        // If $modified is true, we must not send back an ETag.
1083
-        $modified = false;
1084
-        if (!$this->emit('beforeWriteContent', [$uri, $node, &$data, &$modified])) return false;
1085
-
1086
-        $etag = $node->put($data);
1087
-        if ($modified) $etag = null;
1088
-        $this->emit('afterWriteContent', [$uri, $node]);
1089
-
1090
-        return true;
1091
-    }
1092
-
1093
-
1094
-
1095
-    /**
1096
-     * This method is invoked by sub-systems creating a new directory.
1097
-     *
1098
-     * @param string $uri
1099
-     * @return void
1100
-     */
1101
-    public function createDirectory($uri) {
1102
-
1103
-        $this->createCollection($uri, new MkCol(['{DAV:}collection'], []));
1104
-
1105
-    }
1106
-
1107
-    /**
1108
-     * Use this method to create a new collection
1109
-     *
1110
-     * @param string $uri The new uri
1111
-     * @param MkCol $mkCol
1112
-     * @return array|null
1113
-     */
1114
-    public function createCollection($uri, MkCol $mkCol) {
1115
-
1116
-        list($parentUri, $newName) = URLUtil::splitPath($uri);
1117
-
1118
-        // Making sure the parent exists
1119
-        try {
1120
-            $parent = $this->tree->getNodeForPath($parentUri);
1121
-
1122
-        } catch (Exception\NotFound $e) {
1123
-            throw new Exception\Conflict('Parent node does not exist');
1124
-
1125
-        }
1126
-
1127
-        // Making sure the parent is a collection
1128
-        if (!$parent instanceof ICollection) {
1129
-            throw new Exception\Conflict('Parent node is not a collection');
1130
-        }
1131
-
1132
-        // Making sure the child does not already exist
1133
-        try {
1134
-            $parent->getChild($newName);
1135
-
1136
-            // If we got here.. it means there's already a node on that url, and we need to throw a 405
1137
-            throw new Exception\MethodNotAllowed('The resource you tried to create already exists');
1138
-
1139
-        } catch (Exception\NotFound $e) {
1140
-            // NotFound is the expected behavior.
1141
-        }
1142
-
1143
-
1144
-        if (!$this->emit('beforeBind', [$uri])) return;
1145
-
1146
-        if ($parent instanceof IExtendedCollection) {
1147
-
1148
-            /**
1149
-             * If the parent is an instance of IExtendedCollection, it means that
1150
-             * we can pass the MkCol object directly as it may be able to store
1151
-             * properties immediately.
1152
-             */
1153
-            $parent->createExtendedCollection($newName, $mkCol);
1154
-
1155
-        } else {
1156
-
1157
-            /**
1158
-             * If the parent is a standard ICollection, it means only
1159
-             * 'standard' collections can be created, so we should fail any
1160
-             * MKCOL operation that carries extra resourcetypes.
1161
-             */
1162
-            if (count($mkCol->getResourceType()) > 1) {
1163
-                throw new Exception\InvalidResourceType('The {DAV:}resourcetype you specified is not supported here.');
1164
-            }
1165
-
1166
-            $parent->createDirectory($newName);
1167
-
1168
-        }
1169
-
1170
-        // If there are any properties that have not been handled/stored,
1171
-        // we ask the 'propPatch' event to handle them. This will allow for
1172
-        // example the propertyStorage system to store properties upon MKCOL.
1173
-        if ($mkCol->getRemainingMutations()) {
1174
-            $this->emit('propPatch', [$uri, $mkCol]);
1175
-        }
1176
-        $success = $mkCol->commit();
1177
-
1178
-        if (!$success) {
1179
-            $result = $mkCol->getResult();
1180
-            // generateMkCol needs the href key to exist.
1181
-            $result['href'] = $uri;
1182
-            return $result;
1183
-        }
1184
-
1185
-        $this->tree->markDirty($parentUri);
1186
-        $this->emit('afterBind', [$uri]);
1187
-
1188
-    }
1189
-
1190
-    /**
1191
-     * This method updates a resource's properties
1192
-     *
1193
-     * The properties array must be a list of properties. Array-keys are
1194
-     * property names in clarknotation, array-values are it's values.
1195
-     * If a property must be deleted, the value should be null.
1196
-     *
1197
-     * Note that this request should either completely succeed, or
1198
-     * completely fail.
1199
-     *
1200
-     * The response is an array with properties for keys, and http status codes
1201
-     * as their values.
1202
-     *
1203
-     * @param string $path
1204
-     * @param array $properties
1205
-     * @return array
1206
-     */
1207
-    public function updateProperties($path, array $properties) {
1208
-
1209
-        $propPatch = new PropPatch($properties);
1210
-        $this->emit('propPatch', [$path, $propPatch]);
1211
-        $propPatch->commit();
1212
-
1213
-        return $propPatch->getResult();
1214
-
1215
-    }
1216
-
1217
-    /**
1218
-     * This method checks the main HTTP preconditions.
1219
-     *
1220
-     * Currently these are:
1221
-     *   * If-Match
1222
-     *   * If-None-Match
1223
-     *   * If-Modified-Since
1224
-     *   * If-Unmodified-Since
1225
-     *
1226
-     * The method will return true if all preconditions are met
1227
-     * The method will return false, or throw an exception if preconditions
1228
-     * failed. If false is returned the operation should be aborted, and
1229
-     * the appropriate HTTP response headers are already set.
1230
-     *
1231
-     * Normally this method will throw 412 Precondition Failed for failures
1232
-     * related to If-None-Match, If-Match and If-Unmodified Since. It will
1233
-     * set the status to 304 Not Modified for If-Modified_since.
1234
-     *
1235
-     * @param RequestInterface $request
1236
-     * @param ResponseInterface $response
1237
-     * @return bool
1238
-     */
1239
-    public function checkPreconditions(RequestInterface $request, ResponseInterface $response) {
1240
-
1241
-        $path = $request->getPath();
1242
-        $node = null;
1243
-        $lastMod = null;
1244
-        $etag = null;
1245
-
1246
-        if ($ifMatch = $request->getHeader('If-Match')) {
1247
-
1248
-            // If-Match contains an entity tag. Only if the entity-tag
1249
-            // matches we are allowed to make the request succeed.
1250
-            // If the entity-tag is '*' we are only allowed to make the
1251
-            // request succeed if a resource exists at that url.
1252
-            try {
1253
-                $node = $this->tree->getNodeForPath($path);
1254
-            } catch (Exception\NotFound $e) {
1255
-                throw new Exception\PreconditionFailed('An If-Match header was specified and the resource did not exist', 'If-Match');
1256
-            }
1257
-
1258
-            // Only need to check entity tags if they are not *
1259
-            if ($ifMatch !== '*') {
1260
-
1261
-                // There can be multiple ETags
1262
-                $ifMatch = explode(',', $ifMatch);
1263
-                $haveMatch = false;
1264
-                foreach ($ifMatch as $ifMatchItem) {
1265
-
1266
-                    // Stripping any extra spaces
1267
-                    $ifMatchItem = trim($ifMatchItem, ' ');
1268
-
1269
-                    $etag = $node instanceof IFile ? $node->getETag() : null;
1270
-                    if ($etag === $ifMatchItem) {
1271
-                        $haveMatch = true;
1272
-                    } else {
1273
-                        // Evolution has a bug where it sometimes prepends the "
1274
-                        // with a \. This is our workaround.
1275
-                        if (str_replace('\\"', '"', $ifMatchItem) === $etag) {
1276
-                            $haveMatch = true;
1277
-                        }
1278
-                    }
1279
-
1280
-                }
1281
-                if (!$haveMatch) {
1282
-                    if ($etag) $response->setHeader('ETag', $etag);
1283
-                     throw new Exception\PreconditionFailed('An If-Match header was specified, but none of the specified the ETags matched.', 'If-Match');
1284
-                }
1285
-            }
1286
-        }
1287
-
1288
-        if ($ifNoneMatch = $request->getHeader('If-None-Match')) {
1289
-
1290
-            // The If-None-Match header contains an ETag.
1291
-            // Only if the ETag does not match the current ETag, the request will succeed
1292
-            // The header can also contain *, in which case the request
1293
-            // will only succeed if the entity does not exist at all.
1294
-            $nodeExists = true;
1295
-            if (!$node) {
1296
-                try {
1297
-                    $node = $this->tree->getNodeForPath($path);
1298
-                } catch (Exception\NotFound $e) {
1299
-                    $nodeExists = false;
1300
-                }
1301
-            }
1302
-            if ($nodeExists) {
1303
-                $haveMatch = false;
1304
-                if ($ifNoneMatch === '*') $haveMatch = true;
1305
-                else {
1306
-
1307
-                    // There might be multiple ETags
1308
-                    $ifNoneMatch = explode(',', $ifNoneMatch);
1309
-                    $etag = $node instanceof IFile ? $node->getETag() : null;
1310
-
1311
-                    foreach ($ifNoneMatch as $ifNoneMatchItem) {
1312
-
1313
-                        // Stripping any extra spaces
1314
-                        $ifNoneMatchItem = trim($ifNoneMatchItem, ' ');
1315
-
1316
-                        if ($etag === $ifNoneMatchItem) $haveMatch = true;
1317
-
1318
-                    }
1319
-
1320
-                }
1321
-
1322
-                if ($haveMatch) {
1323
-                    if ($etag) $response->setHeader('ETag', $etag);
1324
-                    if ($request->getMethod() === 'GET') {
1325
-                        $response->setStatus(304);
1326
-                        return false;
1327
-                    } else {
1328
-                        throw new Exception\PreconditionFailed('An If-None-Match header was specified, but the ETag matched (or * was specified).', 'If-None-Match');
1329
-                    }
1330
-                }
1331
-            }
1332
-
1333
-        }
1334
-
1335
-        if (!$ifNoneMatch && ($ifModifiedSince = $request->getHeader('If-Modified-Since'))) {
1336
-
1337
-            // The If-Modified-Since header contains a date. We
1338
-            // will only return the entity if it has been changed since
1339
-            // that date. If it hasn't been changed, we return a 304
1340
-            // header
1341
-            // Note that this header only has to be checked if there was no If-None-Match header
1342
-            // as per the HTTP spec.
1343
-            $date = HTTP\Util::parseHTTPDate($ifModifiedSince);
1344
-
1345
-            if ($date) {
1346
-                if (is_null($node)) {
1347
-                    $node = $this->tree->getNodeForPath($path);
1348
-                }
1349
-                $lastMod = $node->getLastModified();
1350
-                if ($lastMod) {
1351
-                    $lastMod = new \DateTime('@' . $lastMod);
1352
-                    if ($lastMod <= $date) {
1353
-                        $response->setStatus(304);
1354
-                        $response->setHeader('Last-Modified', HTTP\Util::toHTTPDate($lastMod));
1355
-                        return false;
1356
-                    }
1357
-                }
1358
-            }
1359
-        }
1360
-
1361
-        if ($ifUnmodifiedSince = $request->getHeader('If-Unmodified-Since')) {
1362
-
1363
-            // The If-Unmodified-Since will allow allow the request if the
1364
-            // entity has not changed since the specified date.
1365
-            $date = HTTP\Util::parseHTTPDate($ifUnmodifiedSince);
1366
-
1367
-            // We must only check the date if it's valid
1368
-            if ($date) {
1369
-                if (is_null($node)) {
1370
-                    $node = $this->tree->getNodeForPath($path);
1371
-                }
1372
-                $lastMod = $node->getLastModified();
1373
-                if ($lastMod) {
1374
-                    $lastMod = new \DateTime('@' . $lastMod);
1375
-                    if ($lastMod > $date) {
1376
-                        throw new Exception\PreconditionFailed('An If-Unmodified-Since header was specified, but the entity has been changed since the specified date.', 'If-Unmodified-Since');
1377
-                    }
1378
-                }
1379
-            }
1380
-
1381
-        }
1382
-
1383
-        // Now the hardest, the If: header. The If: header can contain multiple
1384
-        // urls, ETags and so-called 'state tokens'.
1385
-        //
1386
-        // Examples of state tokens include lock-tokens (as defined in rfc4918)
1387
-        // and sync-tokens (as defined in rfc6578).
1388
-        //
1389
-        // The only proper way to deal with these, is to emit events, that a
1390
-        // Sync and Lock plugin can pick up.
1391
-        $ifConditions = $this->getIfConditions($request);
1392
-
1393
-        foreach ($ifConditions as $kk => $ifCondition) {
1394
-            foreach ($ifCondition['tokens'] as $ii => $token) {
1395
-                $ifConditions[$kk]['tokens'][$ii]['validToken'] = false;
1396
-            }
1397
-        }
1398
-
1399
-        // Plugins are responsible for validating all the tokens.
1400
-        // If a plugin deemed a token 'valid', it will set 'validToken' to
1401
-        // true.
1402
-        $this->emit('validateTokens', [ $request, &$ifConditions ]);
1403
-
1404
-        // Now we're going to analyze the result.
1405
-
1406
-        // Every ifCondition needs to validate to true, so we exit as soon as
1407
-        // we have an invalid condition.
1408
-        foreach ($ifConditions as $ifCondition) {
1409
-
1410
-            $uri = $ifCondition['uri'];
1411
-            $tokens = $ifCondition['tokens'];
1412
-
1413
-            // We only need 1 valid token for the condition to succeed.
1414
-            foreach ($tokens as $token) {
1415
-
1416
-                $tokenValid = $token['validToken'] || !$token['token'];
1417
-
1418
-                $etagValid = false;
1419
-                if (!$token['etag']) {
1420
-                    $etagValid = true;
1421
-                }
1422
-                // Checking the ETag, only if the token was already deamed
1423
-                // valid and there is one.
1424
-                if ($token['etag'] && $tokenValid) {
1425
-
1426
-                    // The token was valid, and there was an ETag. We must
1427
-                    // grab the current ETag and check it.
1428
-                    $node = $this->tree->getNodeForPath($uri);
1429
-                    $etagValid = $node instanceof IFile && $node->getETag() == $token['etag'];
1430
-
1431
-                }
1432
-
1433
-
1434
-                if (($tokenValid && $etagValid) ^ $token['negate']) {
1435
-                    // Both were valid, so we can go to the next condition.
1436
-                    continue 2;
1437
-                }
1438
-
1439
-
1440
-            }
1441
-
1442
-            // If we ended here, it means there was no valid ETag + token
1443
-            // combination found for the current condition. This means we fail!
1444
-            throw new Exception\PreconditionFailed('Failed to find a valid token/etag combination for ' . $uri, 'If');
1445
-
1446
-        }
1447
-
1448
-        return true;
1449
-
1450
-    }
1451
-
1452
-    /**
1453
-     * This method is created to extract information from the WebDAV HTTP 'If:' header
1454
-     *
1455
-     * The If header can be quite complex, and has a bunch of features. We're using a regex to extract all relevant information
1456
-     * The function will return an array, containing structs with the following keys
1457
-     *
1458
-     *   * uri   - the uri the condition applies to.
1459
-     *   * tokens - The lock token. another 2 dimensional array containing 3 elements
1460
-     *
1461
-     * Example 1:
1462
-     *
1463
-     * If: (<opaquelocktoken:181d4fae-7d8c-11d0-a765-00a0c91e6bf2>)
1464
-     *
1465
-     * Would result in:
1466
-     *
1467
-     * [
1468
-     *    [
1469
-     *       'uri' => '/request/uri',
1470
-     *       'tokens' => [
1471
-     *          [
1472
-     *              [
1473
-     *                  'negate' => false,
1474
-     *                  'token'  => 'opaquelocktoken:181d4fae-7d8c-11d0-a765-00a0c91e6bf2',
1475
-     *                  'etag'   => ""
1476
-     *              ]
1477
-     *          ]
1478
-     *       ],
1479
-     *    ]
1480
-     * ]
1481
-     *
1482
-     * Example 2:
1483
-     *
1484
-     * If: </path/> (Not <opaquelocktoken:181d4fae-7d8c-11d0-a765-00a0c91e6bf2> ["Im An ETag"]) (["Another ETag"]) </path2/> (Not ["Path2 ETag"])
1485
-     *
1486
-     * Would result in:
1487
-     *
1488
-     * [
1489
-     *    [
1490
-     *       'uri' => 'path',
1491
-     *       'tokens' => [
1492
-     *          [
1493
-     *              [
1494
-     *                  'negate' => true,
1495
-     *                  'token'  => 'opaquelocktoken:181d4fae-7d8c-11d0-a765-00a0c91e6bf2',
1496
-     *                  'etag'   => '"Im An ETag"'
1497
-     *              ],
1498
-     *              [
1499
-     *                  'negate' => false,
1500
-     *                  'token'  => '',
1501
-     *                  'etag'   => '"Another ETag"'
1502
-     *              ]
1503
-     *          ]
1504
-     *       ],
1505
-     *    ],
1506
-     *    [
1507
-     *       'uri' => 'path2',
1508
-     *       'tokens' => [
1509
-     *          [
1510
-     *              [
1511
-     *                  'negate' => true,
1512
-     *                  'token'  => '',
1513
-     *                  'etag'   => '"Path2 ETag"'
1514
-     *              ]
1515
-     *          ]
1516
-     *       ],
1517
-     *    ],
1518
-     * ]
1519
-     *
1520
-     * @param RequestInterface $request
1521
-     * @return array
1522
-     */
1523
-    public function getIfConditions(RequestInterface $request) {
1524
-
1525
-        $header = $request->getHeader('If');
1526
-        if (!$header) return [];
1527
-
1528
-        $matches = [];
1529
-
1530
-        $regex = '/(?:\<(?P<uri>.*?)\>\s)?\((?P<not>Not\s)?(?:\<(?P<token>[^\>]*)\>)?(?:\s?)(?:\[(?P<etag>[^\]]*)\])?\)/im';
1531
-        preg_match_all($regex, $header, $matches, PREG_SET_ORDER);
1532
-
1533
-        $conditions = [];
1534
-
1535
-        foreach ($matches as $match) {
1536
-
1537
-            // If there was no uri specified in this match, and there were
1538
-            // already conditions parsed, we add the condition to the list of
1539
-            // conditions for the previous uri.
1540
-            if (!$match['uri'] && count($conditions)) {
1541
-                $conditions[count($conditions) - 1]['tokens'][] = [
1542
-                    'negate' => $match['not'] ? true : false,
1543
-                    'token'  => $match['token'],
1544
-                    'etag'   => isset($match['etag']) ? $match['etag'] : ''
1545
-                ];
1546
-            } else {
1547
-
1548
-                if (!$match['uri']) {
1549
-                    $realUri = $request->getPath();
1550
-                } else {
1551
-                    $realUri = $this->calculateUri($match['uri']);
1552
-                }
1553
-
1554
-                $conditions[] = [
1555
-                    'uri'    => $realUri,
1556
-                    'tokens' => [
1557
-                        [
1558
-                            'negate' => $match['not'] ? true : false,
1559
-                            'token'  => $match['token'],
1560
-                            'etag'   => isset($match['etag']) ? $match['etag'] : ''
1561
-                        ]
1562
-                    ],
1563
-
1564
-                ];
1565
-            }
1566
-
1567
-        }
1568
-
1569
-        return $conditions;
1570
-
1571
-    }
1572
-
1573
-    /**
1574
-     * Returns an array with resourcetypes for a node.
1575
-     *
1576
-     * @param INode $node
1577
-     * @return array
1578
-     */
1579
-    public function getResourceTypeForNode(INode $node) {
1580
-
1581
-        $result = [];
1582
-        foreach ($this->resourceTypeMapping as $className => $resourceType) {
1583
-            if ($node instanceof $className) $result[] = $resourceType;
1584
-        }
1585
-        return $result;
1586
-
1587
-    }
1588
-
1589
-    // }}}
1590
-    // {{{ XML Readers & Writers
1591
-
1592
-
1593
-    /**
1594
-     * Generates a WebDAV propfind response body based on a list of nodes.
1595
-     *
1596
-     * If 'strip404s' is set to true, all 404 responses will be removed.
1597
-     *
1598
-     * @param array $fileProperties The list with nodes
1599
-     * @param bool strip404s
1600
-     * @return string
1601
-     */
1602
-    public function generateMultiStatus(array $fileProperties, $strip404s = false) {
1603
-
1604
-        $xml = [];
1605
-
1606
-        foreach ($fileProperties as $entry) {
1607
-
1608
-            $href = $entry['href'];
1609
-            unset($entry['href']);
1610
-            if ($strip404s) {
1611
-                unset($entry[404]);
1612
-            }
1613
-            $response = new Xml\Element\Response(
1614
-                ltrim($href, '/'),
1615
-                $entry
1616
-            );
1617
-            $xml[] = [
1618
-                'name'  => '{DAV:}response',
1619
-                'value' => $response
1620
-            ];
1621
-
1622
-        }
1623
-        return $this->xml->write('{DAV:}multistatus', $xml, $this->baseUri);
1624
-
1625
-    }
533
+	/**
534
+	 * Turns a URI such as the REQUEST_URI into a local path.
535
+	 *
536
+	 * This method:
537
+	 *   * strips off the base path
538
+	 *   * normalizes the path
539
+	 *   * uri-decodes the path
540
+	 *
541
+	 * @param string $uri
542
+	 * @throws Exception\Forbidden A permission denied exception is thrown whenever there was an attempt to supply a uri outside of the base uri
543
+	 * @return string
544
+	 */
545
+	public function calculateUri($uri) {
546
+
547
+		if ($uri[0] != '/' && strpos($uri, '://')) {
548
+
549
+			$uri = parse_url($uri, PHP_URL_PATH);
550
+
551
+		}
552
+
553
+		$uri = Uri\normalize(str_replace('//', '/', $uri));
554
+		$baseUri = Uri\normalize($this->getBaseUri());
555
+
556
+		if (strpos($uri, $baseUri) === 0) {
557
+
558
+			return trim(URLUtil::decodePath(substr($uri, strlen($baseUri))), '/');
559
+
560
+		// A special case, if the baseUri was accessed without a trailing
561
+		// slash, we'll accept it as well.
562
+		} elseif ($uri . '/' === $baseUri) {
563
+
564
+			return '';
565
+
566
+		} else {
567
+
568
+			throw new Exception\Forbidden('Requested uri (' . $uri . ') is out of base uri (' . $this->getBaseUri() . ')');
569
+
570
+		}
571
+
572
+	}
573
+
574
+	/**
575
+	 * Returns the HTTP depth header
576
+	 *
577
+	 * This method returns the contents of the HTTP depth request header. If the depth header was 'infinity' it will return the Sabre\DAV\Server::DEPTH_INFINITY object
578
+	 * It is possible to supply a default depth value, which is used when the depth header has invalid content, or is completely non-existent
579
+	 *
580
+	 * @param mixed $default
581
+	 * @return int
582
+	 */
583
+	public function getHTTPDepth($default = self::DEPTH_INFINITY) {
584
+
585
+		// If its not set, we'll grab the default
586
+		$depth = $this->httpRequest->getHeader('Depth');
587
+
588
+		if (is_null($depth)) return $default;
589
+
590
+		if ($depth == 'infinity') return self::DEPTH_INFINITY;
591
+
592
+
593
+		// If its an unknown value. we'll grab the default
594
+		if (!ctype_digit($depth)) return $default;
595
+
596
+		return (int)$depth;
597
+
598
+	}
599
+
600
+	/**
601
+	 * Returns the HTTP range header
602
+	 *
603
+	 * This method returns null if there is no well-formed HTTP range request
604
+	 * header or array($start, $end).
605
+	 *
606
+	 * The first number is the offset of the first byte in the range.
607
+	 * The second number is the offset of the last byte in the range.
608
+	 *
609
+	 * If the second offset is null, it should be treated as the offset of the last byte of the entity
610
+	 * If the first offset is null, the second offset should be used to retrieve the last x bytes of the entity
611
+	 *
612
+	 * @return array|null
613
+	 */
614
+	public function getHTTPRange() {
615
+
616
+		$range = $this->httpRequest->getHeader('range');
617
+		if (is_null($range)) return null;
618
+
619
+		// Matching "Range: bytes=1234-5678: both numbers are optional
620
+
621
+		if (!preg_match('/^bytes=([0-9]*)-([0-9]*)$/i', $range, $matches)) return null;
622
+
623
+		if ($matches[1] === '' && $matches[2] === '') return null;
624
+
625
+		return [
626
+			$matches[1] !== '' ? $matches[1] : null,
627
+			$matches[2] !== '' ? $matches[2] : null,
628
+		];
629
+
630
+	}
631
+
632
+	/**
633
+	 * Returns the HTTP Prefer header information.
634
+	 *
635
+	 * The prefer header is defined in:
636
+	 * http://tools.ietf.org/html/draft-snell-http-prefer-14
637
+	 *
638
+	 * This method will return an array with options.
639
+	 *
640
+	 * Currently, the following options may be returned:
641
+	 *  [
642
+	 *      'return-asynch'         => true,
643
+	 *      'return-minimal'        => true,
644
+	 *      'return-representation' => true,
645
+	 *      'wait'                  => 30,
646
+	 *      'strict'                => true,
647
+	 *      'lenient'               => true,
648
+	 *  ]
649
+	 *
650
+	 * This method also supports the Brief header, and will also return
651
+	 * 'return-minimal' if the brief header was set to 't'.
652
+	 *
653
+	 * For the boolean options, false will be returned if the headers are not
654
+	 * specified. For the integer options it will be 'null'.
655
+	 *
656
+	 * @return array
657
+	 */
658
+	public function getHTTPPrefer() {
659
+
660
+		$result = [
661
+			// can be true or false
662
+			'respond-async' => false,
663
+			// Could be set to 'representation' or 'minimal'.
664
+			'return'        => null,
665
+			// Used as a timeout, is usually a number.
666
+			'wait'          => null,
667
+			// can be 'strict' or 'lenient'.
668
+			'handling'      => false,
669
+		];
670
+
671
+		if ($prefer = $this->httpRequest->getHeader('Prefer')) {
672
+
673
+			$result = array_merge(
674
+				$result,
675
+				\Sabre\HTTP\parsePrefer($prefer)
676
+			);
677
+
678
+		} elseif ($this->httpRequest->getHeader('Brief') == 't') {
679
+			$result['return'] = 'minimal';
680
+		}
681
+
682
+		return $result;
683
+
684
+	}
685
+
686
+
687
+	/**
688
+	 * Returns information about Copy and Move requests
689
+	 *
690
+	 * This function is created to help getting information about the source and the destination for the
691
+	 * WebDAV MOVE and COPY HTTP request. It also validates a lot of information and throws proper exceptions
692
+	 *
693
+	 * The returned value is an array with the following keys:
694
+	 *   * destination - Destination path
695
+	 *   * destinationExists - Whether or not the destination is an existing url (and should therefore be overwritten)
696
+	 *
697
+	 * @param RequestInterface $request
698
+	 * @throws Exception\BadRequest upon missing or broken request headers
699
+	 * @throws Exception\UnsupportedMediaType when trying to copy into a
700
+	 *         non-collection.
701
+	 * @throws Exception\PreconditionFailed If overwrite is set to false, but
702
+	 *         the destination exists.
703
+	 * @throws Exception\Forbidden when source and destination paths are
704
+	 *         identical.
705
+	 * @throws Exception\Conflict When trying to copy a node into its own
706
+	 *         subtree.
707
+	 * @return array
708
+	 */
709
+	public function getCopyAndMoveInfo(RequestInterface $request) {
710
+
711
+		// Collecting the relevant HTTP headers
712
+		if (!$request->getHeader('Destination')) throw new Exception\BadRequest('The destination header was not supplied');
713
+		$destination = $this->calculateUri($request->getHeader('Destination'));
714
+		$overwrite = $request->getHeader('Overwrite');
715
+		if (!$overwrite) $overwrite = 'T';
716
+		if (strtoupper($overwrite) == 'T') $overwrite = true;
717
+		elseif (strtoupper($overwrite) == 'F') $overwrite = false;
718
+		// We need to throw a bad request exception, if the header was invalid
719
+		else throw new Exception\BadRequest('The HTTP Overwrite header should be either T or F');
720
+
721
+		list($destinationDir) = URLUtil::splitPath($destination);
722
+
723
+		try {
724
+			$destinationParent = $this->tree->getNodeForPath($destinationDir);
725
+			if (!($destinationParent instanceof ICollection)) throw new Exception\UnsupportedMediaType('The destination node is not a collection');
726
+		} catch (Exception\NotFound $e) {
727
+
728
+			// If the destination parent node is not found, we throw a 409
729
+			throw new Exception\Conflict('The destination node is not found');
730
+		}
731
+
732
+		try {
733
+
734
+			$destinationNode = $this->tree->getNodeForPath($destination);
735
+
736
+			// If this succeeded, it means the destination already exists
737
+			// we'll need to throw precondition failed in case overwrite is false
738
+			if (!$overwrite) throw new Exception\PreconditionFailed('The destination node already exists, and the overwrite header is set to false', 'Overwrite');
739
+
740
+		} catch (Exception\NotFound $e) {
741
+
742
+			// Destination didn't exist, we're all good
743
+			$destinationNode = false;
744
+
745
+		}
746
+
747
+		$requestPath = $request->getPath();
748
+		if ($destination === $requestPath) {
749
+			throw new Exception\Forbidden('Source and destination uri are identical.');
750
+		}
751
+		if (substr($destination, 0, strlen($requestPath) + 1) === $requestPath . '/') {
752
+			throw new Exception\Conflict('The destination may not be part of the same subtree as the source path.');
753
+		}
754
+
755
+		// These are the three relevant properties we need to return
756
+		return [
757
+			'destination'       => $destination,
758
+			'destinationExists' => !!$destinationNode,
759
+			'destinationNode'   => $destinationNode,
760
+		];
761
+
762
+	}
763
+
764
+	/**
765
+	 * Returns a list of properties for a path
766
+	 *
767
+	 * This is a simplified version getPropertiesForPath. If you aren't
768
+	 * interested in status codes, but you just want to have a flat list of
769
+	 * properties, use this method.
770
+	 *
771
+	 * Please note though that any problems related to retrieving properties,
772
+	 * such as permission issues will just result in an empty array being
773
+	 * returned.
774
+	 *
775
+	 * @param string $path
776
+	 * @param array $propertyNames
777
+	 */
778
+	public function getProperties($path, $propertyNames) {
779
+
780
+		$result = $this->getPropertiesForPath($path, $propertyNames, 0);
781
+		if (isset($result[0][200])) {
782
+			return $result[0][200];
783
+		} else {
784
+			return [];
785
+		}
786
+
787
+	}
788
+
789
+	/**
790
+	 * A kid-friendly way to fetch properties for a node's children.
791
+	 *
792
+	 * The returned array will be indexed by the path of the of child node.
793
+	 * Only properties that are actually found will be returned.
794
+	 *
795
+	 * The parent node will not be returned.
796
+	 *
797
+	 * @param string $path
798
+	 * @param array $propertyNames
799
+	 * @return array
800
+	 */
801
+	public function getPropertiesForChildren($path, $propertyNames) {
802
+
803
+		$result = [];
804
+		foreach ($this->getPropertiesForPath($path, $propertyNames, 1) as $k => $row) {
805
+
806
+			// Skipping the parent path
807
+			if ($k === 0) continue;
808
+
809
+			$result[$row['href']] = $row[200];
810
+
811
+		}
812
+		return $result;
813
+
814
+	}
815
+
816
+	/**
817
+	 * Returns a list of HTTP headers for a particular resource
818
+	 *
819
+	 * The generated http headers are based on properties provided by the
820
+	 * resource. The method basically provides a simple mapping between
821
+	 * DAV property and HTTP header.
822
+	 *
823
+	 * The headers are intended to be used for HEAD and GET requests.
824
+	 *
825
+	 * @param string $path
826
+	 * @return array
827
+	 */
828
+	public function getHTTPHeaders($path) {
829
+
830
+		$propertyMap = [
831
+			'{DAV:}getcontenttype'   => 'Content-Type',
832
+			'{DAV:}getcontentlength' => 'Content-Length',
833
+			'{DAV:}getlastmodified'  => 'Last-Modified',
834
+			'{DAV:}getetag'          => 'ETag',
835
+		];
836
+
837
+		$properties = $this->getProperties($path, array_keys($propertyMap));
838
+
839
+		$headers = [];
840
+		foreach ($propertyMap as $property => $header) {
841
+			if (!isset($properties[$property])) continue;
842
+
843
+			if (is_scalar($properties[$property])) {
844
+				$headers[$header] = $properties[$property];
845
+
846
+			// GetLastModified gets special cased
847
+			} elseif ($properties[$property] instanceof Xml\Property\GetLastModified) {
848
+				$headers[$header] = HTTP\Util::toHTTPDate($properties[$property]->getTime());
849
+			}
850
+
851
+		}
852
+
853
+		return $headers;
854
+
855
+	}
856
+
857
+	/**
858
+	 * Small helper to support PROPFIND with DEPTH_INFINITY.
859
+	 *
860
+	 * @param array[] $propFindRequests
861
+	 * @param PropFind $propFind
862
+	 * @return void
863
+	 */
864
+	private function addPathNodesRecursively(&$propFindRequests, PropFind $propFind) {
865
+
866
+		$newDepth = $propFind->getDepth();
867
+		$path = $propFind->getPath();
868
+
869
+		if ($newDepth !== self::DEPTH_INFINITY) {
870
+			$newDepth--;
871
+		}
872
+
873
+		foreach ($this->tree->getChildren($path) as $childNode) {
874
+			$subPropFind = clone $propFind;
875
+			$subPropFind->setDepth($newDepth);
876
+			if ($path !== '') {
877
+				$subPath = $path . '/' . $childNode->getName();
878
+			} else {
879
+				$subPath = $childNode->getName();
880
+			}
881
+			$subPropFind->setPath($subPath);
882
+
883
+			$propFindRequests[] = [
884
+				$subPropFind,
885
+				$childNode
886
+			];
887
+
888
+			if (($newDepth === self::DEPTH_INFINITY || $newDepth >= 1) && $childNode instanceof ICollection) {
889
+				$this->addPathNodesRecursively($propFindRequests, $subPropFind);
890
+			}
891
+
892
+		}
893
+	}
894
+
895
+	/**
896
+	 * Returns a list of properties for a given path
897
+	 *
898
+	 * The path that should be supplied should have the baseUrl stripped out
899
+	 * The list of properties should be supplied in Clark notation. If the list is empty
900
+	 * 'allprops' is assumed.
901
+	 *
902
+	 * If a depth of 1 is requested child elements will also be returned.
903
+	 *
904
+	 * @param string $path
905
+	 * @param array $propertyNames
906
+	 * @param int $depth
907
+	 * @return array
908
+	 */
909
+	public function getPropertiesForPath($path, $propertyNames = [], $depth = 0) {
910
+
911
+		// The only two options for the depth of a propfind is 0 or 1 - as long as depth infinity is not enabled
912
+		if (!$this->enablePropfindDepthInfinity && $depth != 0) $depth = 1;
913
+
914
+		$path = trim($path, '/');
915
+
916
+		$propFindType = $propertyNames ? PropFind::NORMAL : PropFind::ALLPROPS;
917
+		$propFind = new PropFind($path, (array)$propertyNames, $depth, $propFindType);
918
+
919
+		$parentNode = $this->tree->getNodeForPath($path);
920
+
921
+		$propFindRequests = [[
922
+			$propFind,
923
+			$parentNode
924
+		]];
925
+
926
+		if (($depth > 0 || $depth === self::DEPTH_INFINITY) && $parentNode instanceof ICollection) {
927
+			$this->addPathNodesRecursively($propFindRequests, $propFind);
928
+		}
929
+
930
+		$returnPropertyList = [];
931
+
932
+		foreach ($propFindRequests as $propFindRequest) {
933
+
934
+			list($propFind, $node) = $propFindRequest;
935
+			$r = $this->getPropertiesByNode($propFind, $node);
936
+			if ($r) {
937
+				$result = $propFind->getResultForMultiStatus();
938
+				$result['href'] = $propFind->getPath();
939
+
940
+				// WebDAV recommends adding a slash to the path, if the path is
941
+				// a collection.
942
+				// Furthermore, iCal also demands this to be the case for
943
+				// principals. This is non-standard, but we support it.
944
+				$resourceType = $this->getResourceTypeForNode($node);
945
+				if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) {
946
+					$result['href'] .= '/';
947
+				}
948
+				$returnPropertyList[] = $result;
949
+			}
950
+
951
+		}
952
+
953
+		return $returnPropertyList;
954
+
955
+	}
956
+
957
+	/**
958
+	 * Returns a list of properties for a list of paths.
959
+	 *
960
+	 * The path that should be supplied should have the baseUrl stripped out
961
+	 * The list of properties should be supplied in Clark notation. If the list is empty
962
+	 * 'allprops' is assumed.
963
+	 *
964
+	 * The result is returned as an array, with paths for it's keys.
965
+	 * The result may be returned out of order.
966
+	 *
967
+	 * @param array $paths
968
+	 * @param array $propertyNames
969
+	 * @return array
970
+	 */
971
+	public function getPropertiesForMultiplePaths(array $paths, array $propertyNames = []) {
972
+
973
+		$result = [
974
+		];
975
+
976
+		$nodes = $this->tree->getMultipleNodes($paths);
977
+
978
+		foreach ($nodes as $path => $node) {
979
+
980
+			$propFind = new PropFind($path, $propertyNames);
981
+			$r = $this->getPropertiesByNode($propFind, $node);
982
+			if ($r) {
983
+				$result[$path] = $propFind->getResultForMultiStatus();
984
+				$result[$path]['href'] = $path;
985
+
986
+				$resourceType = $this->getResourceTypeForNode($node);
987
+				if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) {
988
+					$result[$path]['href'] .= '/';
989
+				}
990
+			}
991
+
992
+		}
993
+
994
+		return $result;
995
+
996
+	}
997
+
998
+
999
+	/**
1000
+	 * Determines all properties for a node.
1001
+	 *
1002
+	 * This method tries to grab all properties for a node. This method is used
1003
+	 * internally getPropertiesForPath and a few others.
1004
+	 *
1005
+	 * It could be useful to call this, if you already have an instance of your
1006
+	 * target node and simply want to run through the system to get a correct
1007
+	 * list of properties.
1008
+	 *
1009
+	 * @param PropFind $propFind
1010
+	 * @param INode $node
1011
+	 * @return bool
1012
+	 */
1013
+	public function getPropertiesByNode(PropFind $propFind, INode $node) {
1014
+
1015
+		return $this->emit('propFind', [$propFind, $node]);
1016
+
1017
+	}
1018
+
1019
+	/**
1020
+	 * This method is invoked by sub-systems creating a new file.
1021
+	 *
1022
+	 * Currently this is done by HTTP PUT and HTTP LOCK (in the Locks_Plugin).
1023
+	 * It was important to get this done through a centralized function,
1024
+	 * allowing plugins to intercept this using the beforeCreateFile event.
1025
+	 *
1026
+	 * This method will return true if the file was actually created
1027
+	 *
1028
+	 * @param string   $uri
1029
+	 * @param resource $data
1030
+	 * @param string   $etag
1031
+	 * @return bool
1032
+	 */
1033
+	public function createFile($uri, $data, &$etag = null) {
1034
+
1035
+		list($dir, $name) = URLUtil::splitPath($uri);
1036
+
1037
+		if (!$this->emit('beforeBind', [$uri])) return false;
1038
+
1039
+		$parent = $this->tree->getNodeForPath($dir);
1040
+		if (!$parent instanceof ICollection) {
1041
+			throw new Exception\Conflict('Files can only be created as children of collections');
1042
+		}
1043
+
1044
+		// It is possible for an event handler to modify the content of the
1045
+		// body, before it gets written. If this is the case, $modified
1046
+		// should be set to true.
1047
+		//
1048
+		// If $modified is true, we must not send back an ETag.
1049
+		$modified = false;
1050
+		if (!$this->emit('beforeCreateFile', [$uri, &$data, $parent, &$modified])) return false;
1051
+
1052
+		$etag = $parent->createFile($name, $data);
1053
+
1054
+		if ($modified) $etag = null;
1055
+
1056
+		$this->tree->markDirty($dir . '/' . $name);
1057
+
1058
+		$this->emit('afterBind', [$uri]);
1059
+		$this->emit('afterCreateFile', [$uri, $parent]);
1060
+
1061
+		return true;
1062
+	}
1063
+
1064
+	/**
1065
+	 * This method is invoked by sub-systems updating a file.
1066
+	 *
1067
+	 * This method will return true if the file was actually updated
1068
+	 *
1069
+	 * @param string   $uri
1070
+	 * @param resource $data
1071
+	 * @param string   $etag
1072
+	 * @return bool
1073
+	 */
1074
+	public function updateFile($uri, $data, &$etag = null) {
1075
+
1076
+		$node = $this->tree->getNodeForPath($uri);
1077
+
1078
+		// It is possible for an event handler to modify the content of the
1079
+		// body, before it gets written. If this is the case, $modified
1080
+		// should be set to true.
1081
+		//
1082
+		// If $modified is true, we must not send back an ETag.
1083
+		$modified = false;
1084
+		if (!$this->emit('beforeWriteContent', [$uri, $node, &$data, &$modified])) return false;
1085
+
1086
+		$etag = $node->put($data);
1087
+		if ($modified) $etag = null;
1088
+		$this->emit('afterWriteContent', [$uri, $node]);
1089
+
1090
+		return true;
1091
+	}
1092
+
1093
+
1094
+
1095
+	/**
1096
+	 * This method is invoked by sub-systems creating a new directory.
1097
+	 *
1098
+	 * @param string $uri
1099
+	 * @return void
1100
+	 */
1101
+	public function createDirectory($uri) {
1102
+
1103
+		$this->createCollection($uri, new MkCol(['{DAV:}collection'], []));
1104
+
1105
+	}
1106
+
1107
+	/**
1108
+	 * Use this method to create a new collection
1109
+	 *
1110
+	 * @param string $uri The new uri
1111
+	 * @param MkCol $mkCol
1112
+	 * @return array|null
1113
+	 */
1114
+	public function createCollection($uri, MkCol $mkCol) {
1115
+
1116
+		list($parentUri, $newName) = URLUtil::splitPath($uri);
1117
+
1118
+		// Making sure the parent exists
1119
+		try {
1120
+			$parent = $this->tree->getNodeForPath($parentUri);
1121
+
1122
+		} catch (Exception\NotFound $e) {
1123
+			throw new Exception\Conflict('Parent node does not exist');
1124
+
1125
+		}
1126
+
1127
+		// Making sure the parent is a collection
1128
+		if (!$parent instanceof ICollection) {
1129
+			throw new Exception\Conflict('Parent node is not a collection');
1130
+		}
1131
+
1132
+		// Making sure the child does not already exist
1133
+		try {
1134
+			$parent->getChild($newName);
1135
+
1136
+			// If we got here.. it means there's already a node on that url, and we need to throw a 405
1137
+			throw new Exception\MethodNotAllowed('The resource you tried to create already exists');
1138
+
1139
+		} catch (Exception\NotFound $e) {
1140
+			// NotFound is the expected behavior.
1141
+		}
1142
+
1143
+
1144
+		if (!$this->emit('beforeBind', [$uri])) return;
1145
+
1146
+		if ($parent instanceof IExtendedCollection) {
1147
+
1148
+			/**
1149
+			 * If the parent is an instance of IExtendedCollection, it means that
1150
+			 * we can pass the MkCol object directly as it may be able to store
1151
+			 * properties immediately.
1152
+			 */
1153
+			$parent->createExtendedCollection($newName, $mkCol);
1154
+
1155
+		} else {
1156
+
1157
+			/**
1158
+			 * If the parent is a standard ICollection, it means only
1159
+			 * 'standard' collections can be created, so we should fail any
1160
+			 * MKCOL operation that carries extra resourcetypes.
1161
+			 */
1162
+			if (count($mkCol->getResourceType()) > 1) {
1163
+				throw new Exception\InvalidResourceType('The {DAV:}resourcetype you specified is not supported here.');
1164
+			}
1165
+
1166
+			$parent->createDirectory($newName);
1167
+
1168
+		}
1169
+
1170
+		// If there are any properties that have not been handled/stored,
1171
+		// we ask the 'propPatch' event to handle them. This will allow for
1172
+		// example the propertyStorage system to store properties upon MKCOL.
1173
+		if ($mkCol->getRemainingMutations()) {
1174
+			$this->emit('propPatch', [$uri, $mkCol]);
1175
+		}
1176
+		$success = $mkCol->commit();
1177
+
1178
+		if (!$success) {
1179
+			$result = $mkCol->getResult();
1180
+			// generateMkCol needs the href key to exist.
1181
+			$result['href'] = $uri;
1182
+			return $result;
1183
+		}
1184
+
1185
+		$this->tree->markDirty($parentUri);
1186
+		$this->emit('afterBind', [$uri]);
1187
+
1188
+	}
1189
+
1190
+	/**
1191
+	 * This method updates a resource's properties
1192
+	 *
1193
+	 * The properties array must be a list of properties. Array-keys are
1194
+	 * property names in clarknotation, array-values are it's values.
1195
+	 * If a property must be deleted, the value should be null.
1196
+	 *
1197
+	 * Note that this request should either completely succeed, or
1198
+	 * completely fail.
1199
+	 *
1200
+	 * The response is an array with properties for keys, and http status codes
1201
+	 * as their values.
1202
+	 *
1203
+	 * @param string $path
1204
+	 * @param array $properties
1205
+	 * @return array
1206
+	 */
1207
+	public function updateProperties($path, array $properties) {
1208
+
1209
+		$propPatch = new PropPatch($properties);
1210
+		$this->emit('propPatch', [$path, $propPatch]);
1211
+		$propPatch->commit();
1212
+
1213
+		return $propPatch->getResult();
1214
+
1215
+	}
1216
+
1217
+	/**
1218
+	 * This method checks the main HTTP preconditions.
1219
+	 *
1220
+	 * Currently these are:
1221
+	 *   * If-Match
1222
+	 *   * If-None-Match
1223
+	 *   * If-Modified-Since
1224
+	 *   * If-Unmodified-Since
1225
+	 *
1226
+	 * The method will return true if all preconditions are met
1227
+	 * The method will return false, or throw an exception if preconditions
1228
+	 * failed. If false is returned the operation should be aborted, and
1229
+	 * the appropriate HTTP response headers are already set.
1230
+	 *
1231
+	 * Normally this method will throw 412 Precondition Failed for failures
1232
+	 * related to If-None-Match, If-Match and If-Unmodified Since. It will
1233
+	 * set the status to 304 Not Modified for If-Modified_since.
1234
+	 *
1235
+	 * @param RequestInterface $request
1236
+	 * @param ResponseInterface $response
1237
+	 * @return bool
1238
+	 */
1239
+	public function checkPreconditions(RequestInterface $request, ResponseInterface $response) {
1240
+
1241
+		$path = $request->getPath();
1242
+		$node = null;
1243
+		$lastMod = null;
1244
+		$etag = null;
1245
+
1246
+		if ($ifMatch = $request->getHeader('If-Match')) {
1247
+
1248
+			// If-Match contains an entity tag. Only if the entity-tag
1249
+			// matches we are allowed to make the request succeed.
1250
+			// If the entity-tag is '*' we are only allowed to make the
1251
+			// request succeed if a resource exists at that url.
1252
+			try {
1253
+				$node = $this->tree->getNodeForPath($path);
1254
+			} catch (Exception\NotFound $e) {
1255
+				throw new Exception\PreconditionFailed('An If-Match header was specified and the resource did not exist', 'If-Match');
1256
+			}
1257
+
1258
+			// Only need to check entity tags if they are not *
1259
+			if ($ifMatch !== '*') {
1260
+
1261
+				// There can be multiple ETags
1262
+				$ifMatch = explode(',', $ifMatch);
1263
+				$haveMatch = false;
1264
+				foreach ($ifMatch as $ifMatchItem) {
1265
+
1266
+					// Stripping any extra spaces
1267
+					$ifMatchItem = trim($ifMatchItem, ' ');
1268
+
1269
+					$etag = $node instanceof IFile ? $node->getETag() : null;
1270
+					if ($etag === $ifMatchItem) {
1271
+						$haveMatch = true;
1272
+					} else {
1273
+						// Evolution has a bug where it sometimes prepends the "
1274
+						// with a \. This is our workaround.
1275
+						if (str_replace('\\"', '"', $ifMatchItem) === $etag) {
1276
+							$haveMatch = true;
1277
+						}
1278
+					}
1279
+
1280
+				}
1281
+				if (!$haveMatch) {
1282
+					if ($etag) $response->setHeader('ETag', $etag);
1283
+					 throw new Exception\PreconditionFailed('An If-Match header was specified, but none of the specified the ETags matched.', 'If-Match');
1284
+				}
1285
+			}
1286
+		}
1287
+
1288
+		if ($ifNoneMatch = $request->getHeader('If-None-Match')) {
1289
+
1290
+			// The If-None-Match header contains an ETag.
1291
+			// Only if the ETag does not match the current ETag, the request will succeed
1292
+			// The header can also contain *, in which case the request
1293
+			// will only succeed if the entity does not exist at all.
1294
+			$nodeExists = true;
1295
+			if (!$node) {
1296
+				try {
1297
+					$node = $this->tree->getNodeForPath($path);
1298
+				} catch (Exception\NotFound $e) {
1299
+					$nodeExists = false;
1300
+				}
1301
+			}
1302
+			if ($nodeExists) {
1303
+				$haveMatch = false;
1304
+				if ($ifNoneMatch === '*') $haveMatch = true;
1305
+				else {
1306
+
1307
+					// There might be multiple ETags
1308
+					$ifNoneMatch = explode(',', $ifNoneMatch);
1309
+					$etag = $node instanceof IFile ? $node->getETag() : null;
1310
+
1311
+					foreach ($ifNoneMatch as $ifNoneMatchItem) {
1312
+
1313
+						// Stripping any extra spaces
1314
+						$ifNoneMatchItem = trim($ifNoneMatchItem, ' ');
1315
+
1316
+						if ($etag === $ifNoneMatchItem) $haveMatch = true;
1317
+
1318
+					}
1319
+
1320
+				}
1321
+
1322
+				if ($haveMatch) {
1323
+					if ($etag) $response->setHeader('ETag', $etag);
1324
+					if ($request->getMethod() === 'GET') {
1325
+						$response->setStatus(304);
1326
+						return false;
1327
+					} else {
1328
+						throw new Exception\PreconditionFailed('An If-None-Match header was specified, but the ETag matched (or * was specified).', 'If-None-Match');
1329
+					}
1330
+				}
1331
+			}
1332
+
1333
+		}
1334
+
1335
+		if (!$ifNoneMatch && ($ifModifiedSince = $request->getHeader('If-Modified-Since'))) {
1336
+
1337
+			// The If-Modified-Since header contains a date. We
1338
+			// will only return the entity if it has been changed since
1339
+			// that date. If it hasn't been changed, we return a 304
1340
+			// header
1341
+			// Note that this header only has to be checked if there was no If-None-Match header
1342
+			// as per the HTTP spec.
1343
+			$date = HTTP\Util::parseHTTPDate($ifModifiedSince);
1344
+
1345
+			if ($date) {
1346
+				if (is_null($node)) {
1347
+					$node = $this->tree->getNodeForPath($path);
1348
+				}
1349
+				$lastMod = $node->getLastModified();
1350
+				if ($lastMod) {
1351
+					$lastMod = new \DateTime('@' . $lastMod);
1352
+					if ($lastMod <= $date) {
1353
+						$response->setStatus(304);
1354
+						$response->setHeader('Last-Modified', HTTP\Util::toHTTPDate($lastMod));
1355
+						return false;
1356
+					}
1357
+				}
1358
+			}
1359
+		}
1360
+
1361
+		if ($ifUnmodifiedSince = $request->getHeader('If-Unmodified-Since')) {
1362
+
1363
+			// The If-Unmodified-Since will allow allow the request if the
1364
+			// entity has not changed since the specified date.
1365
+			$date = HTTP\Util::parseHTTPDate($ifUnmodifiedSince);
1366
+
1367
+			// We must only check the date if it's valid
1368
+			if ($date) {
1369
+				if (is_null($node)) {
1370
+					$node = $this->tree->getNodeForPath($path);
1371
+				}
1372
+				$lastMod = $node->getLastModified();
1373
+				if ($lastMod) {
1374
+					$lastMod = new \DateTime('@' . $lastMod);
1375
+					if ($lastMod > $date) {
1376
+						throw new Exception\PreconditionFailed('An If-Unmodified-Since header was specified, but the entity has been changed since the specified date.', 'If-Unmodified-Since');
1377
+					}
1378
+				}
1379
+			}
1380
+
1381
+		}
1382
+
1383
+		// Now the hardest, the If: header. The If: header can contain multiple
1384
+		// urls, ETags and so-called 'state tokens'.
1385
+		//
1386
+		// Examples of state tokens include lock-tokens (as defined in rfc4918)
1387
+		// and sync-tokens (as defined in rfc6578).
1388
+		//
1389
+		// The only proper way to deal with these, is to emit events, that a
1390
+		// Sync and Lock plugin can pick up.
1391
+		$ifConditions = $this->getIfConditions($request);
1392
+
1393
+		foreach ($ifConditions as $kk => $ifCondition) {
1394
+			foreach ($ifCondition['tokens'] as $ii => $token) {
1395
+				$ifConditions[$kk]['tokens'][$ii]['validToken'] = false;
1396
+			}
1397
+		}
1398
+
1399
+		// Plugins are responsible for validating all the tokens.
1400
+		// If a plugin deemed a token 'valid', it will set 'validToken' to
1401
+		// true.
1402
+		$this->emit('validateTokens', [ $request, &$ifConditions ]);
1403
+
1404
+		// Now we're going to analyze the result.
1405
+
1406
+		// Every ifCondition needs to validate to true, so we exit as soon as
1407
+		// we have an invalid condition.
1408
+		foreach ($ifConditions as $ifCondition) {
1409
+
1410
+			$uri = $ifCondition['uri'];
1411
+			$tokens = $ifCondition['tokens'];
1412
+
1413
+			// We only need 1 valid token for the condition to succeed.
1414
+			foreach ($tokens as $token) {
1415
+
1416
+				$tokenValid = $token['validToken'] || !$token['token'];
1417
+
1418
+				$etagValid = false;
1419
+				if (!$token['etag']) {
1420
+					$etagValid = true;
1421
+				}
1422
+				// Checking the ETag, only if the token was already deamed
1423
+				// valid and there is one.
1424
+				if ($token['etag'] && $tokenValid) {
1425
+
1426
+					// The token was valid, and there was an ETag. We must
1427
+					// grab the current ETag and check it.
1428
+					$node = $this->tree->getNodeForPath($uri);
1429
+					$etagValid = $node instanceof IFile && $node->getETag() == $token['etag'];
1430
+
1431
+				}
1432
+
1433
+
1434
+				if (($tokenValid && $etagValid) ^ $token['negate']) {
1435
+					// Both were valid, so we can go to the next condition.
1436
+					continue 2;
1437
+				}
1438
+
1439
+
1440
+			}
1441
+
1442
+			// If we ended here, it means there was no valid ETag + token
1443
+			// combination found for the current condition. This means we fail!
1444
+			throw new Exception\PreconditionFailed('Failed to find a valid token/etag combination for ' . $uri, 'If');
1445
+
1446
+		}
1447
+
1448
+		return true;
1449
+
1450
+	}
1451
+
1452
+	/**
1453
+	 * This method is created to extract information from the WebDAV HTTP 'If:' header
1454
+	 *
1455
+	 * The If header can be quite complex, and has a bunch of features. We're using a regex to extract all relevant information
1456
+	 * The function will return an array, containing structs with the following keys
1457
+	 *
1458
+	 *   * uri   - the uri the condition applies to.
1459
+	 *   * tokens - The lock token. another 2 dimensional array containing 3 elements
1460
+	 *
1461
+	 * Example 1:
1462
+	 *
1463
+	 * If: (<opaquelocktoken:181d4fae-7d8c-11d0-a765-00a0c91e6bf2>)
1464
+	 *
1465
+	 * Would result in:
1466
+	 *
1467
+	 * [
1468
+	 *    [
1469
+	 *       'uri' => '/request/uri',
1470
+	 *       'tokens' => [
1471
+	 *          [
1472
+	 *              [
1473
+	 *                  'negate' => false,
1474
+	 *                  'token'  => 'opaquelocktoken:181d4fae-7d8c-11d0-a765-00a0c91e6bf2',
1475
+	 *                  'etag'   => ""
1476
+	 *              ]
1477
+	 *          ]
1478
+	 *       ],
1479
+	 *    ]
1480
+	 * ]
1481
+	 *
1482
+	 * Example 2:
1483
+	 *
1484
+	 * If: </path/> (Not <opaquelocktoken:181d4fae-7d8c-11d0-a765-00a0c91e6bf2> ["Im An ETag"]) (["Another ETag"]) </path2/> (Not ["Path2 ETag"])
1485
+	 *
1486
+	 * Would result in:
1487
+	 *
1488
+	 * [
1489
+	 *    [
1490
+	 *       'uri' => 'path',
1491
+	 *       'tokens' => [
1492
+	 *          [
1493
+	 *              [
1494
+	 *                  'negate' => true,
1495
+	 *                  'token'  => 'opaquelocktoken:181d4fae-7d8c-11d0-a765-00a0c91e6bf2',
1496
+	 *                  'etag'   => '"Im An ETag"'
1497
+	 *              ],
1498
+	 *              [
1499
+	 *                  'negate' => false,
1500
+	 *                  'token'  => '',
1501
+	 *                  'etag'   => '"Another ETag"'
1502
+	 *              ]
1503
+	 *          ]
1504
+	 *       ],
1505
+	 *    ],
1506
+	 *    [
1507
+	 *       'uri' => 'path2',
1508
+	 *       'tokens' => [
1509
+	 *          [
1510
+	 *              [
1511
+	 *                  'negate' => true,
1512
+	 *                  'token'  => '',
1513
+	 *                  'etag'   => '"Path2 ETag"'
1514
+	 *              ]
1515
+	 *          ]
1516
+	 *       ],
1517
+	 *    ],
1518
+	 * ]
1519
+	 *
1520
+	 * @param RequestInterface $request
1521
+	 * @return array
1522
+	 */
1523
+	public function getIfConditions(RequestInterface $request) {
1524
+
1525
+		$header = $request->getHeader('If');
1526
+		if (!$header) return [];
1527
+
1528
+		$matches = [];
1529
+
1530
+		$regex = '/(?:\<(?P<uri>.*?)\>\s)?\((?P<not>Not\s)?(?:\<(?P<token>[^\>]*)\>)?(?:\s?)(?:\[(?P<etag>[^\]]*)\])?\)/im';
1531
+		preg_match_all($regex, $header, $matches, PREG_SET_ORDER);
1532
+
1533
+		$conditions = [];
1534
+
1535
+		foreach ($matches as $match) {
1536
+
1537
+			// If there was no uri specified in this match, and there were
1538
+			// already conditions parsed, we add the condition to the list of
1539
+			// conditions for the previous uri.
1540
+			if (!$match['uri'] && count($conditions)) {
1541
+				$conditions[count($conditions) - 1]['tokens'][] = [
1542
+					'negate' => $match['not'] ? true : false,
1543
+					'token'  => $match['token'],
1544
+					'etag'   => isset($match['etag']) ? $match['etag'] : ''
1545
+				];
1546
+			} else {
1547
+
1548
+				if (!$match['uri']) {
1549
+					$realUri = $request->getPath();
1550
+				} else {
1551
+					$realUri = $this->calculateUri($match['uri']);
1552
+				}
1553
+
1554
+				$conditions[] = [
1555
+					'uri'    => $realUri,
1556
+					'tokens' => [
1557
+						[
1558
+							'negate' => $match['not'] ? true : false,
1559
+							'token'  => $match['token'],
1560
+							'etag'   => isset($match['etag']) ? $match['etag'] : ''
1561
+						]
1562
+					],
1563
+
1564
+				];
1565
+			}
1566
+
1567
+		}
1568
+
1569
+		return $conditions;
1570
+
1571
+	}
1572
+
1573
+	/**
1574
+	 * Returns an array with resourcetypes for a node.
1575
+	 *
1576
+	 * @param INode $node
1577
+	 * @return array
1578
+	 */
1579
+	public function getResourceTypeForNode(INode $node) {
1580
+
1581
+		$result = [];
1582
+		foreach ($this->resourceTypeMapping as $className => $resourceType) {
1583
+			if ($node instanceof $className) $result[] = $resourceType;
1584
+		}
1585
+		return $result;
1586
+
1587
+	}
1588
+
1589
+	// }}}
1590
+	// {{{ XML Readers & Writers
1591
+
1592
+
1593
+	/**
1594
+	 * Generates a WebDAV propfind response body based on a list of nodes.
1595
+	 *
1596
+	 * If 'strip404s' is set to true, all 404 responses will be removed.
1597
+	 *
1598
+	 * @param array $fileProperties The list with nodes
1599
+	 * @param bool strip404s
1600
+	 * @return string
1601
+	 */
1602
+	public function generateMultiStatus(array $fileProperties, $strip404s = false) {
1603
+
1604
+		$xml = [];
1605
+
1606
+		foreach ($fileProperties as $entry) {
1607
+
1608
+			$href = $entry['href'];
1609
+			unset($entry['href']);
1610
+			if ($strip404s) {
1611
+				unset($entry[404]);
1612
+			}
1613
+			$response = new Xml\Element\Response(
1614
+				ltrim($href, '/'),
1615
+				$entry
1616
+			);
1617
+			$xml[] = [
1618
+				'name'  => '{DAV:}response',
1619
+				'value' => $response
1620
+			];
1621
+
1622
+		}
1623
+		return $this->xml->write('{DAV:}multistatus', $xml, $this->baseUri);
1624
+
1625
+	}
1626 1626
 
1627 1627
 }
Please login to merge, or discard this patch.
Doc Comments   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -487,7 +487,7 @@  discard block
 block discarded – undo
487 487
      * Returns an array with all the supported HTTP methods for a specific uri.
488 488
      *
489 489
      * @param string $path
490
-     * @return array
490
+     * @return string[]
491 491
      */
492 492
     public function getAllowedMethods($path) {
493 493
 
@@ -577,7 +577,7 @@  discard block
 block discarded – undo
577 577
      * This method returns the contents of the HTTP depth request header. If the depth header was 'infinity' it will return the Sabre\DAV\Server::DEPTH_INFINITY object
578 578
      * It is possible to supply a default depth value, which is used when the depth header has invalid content, or is completely non-existent
579 579
      *
580
-     * @param mixed $default
580
+     * @param integer $default
581 581
      * @return int
582 582
      */
583 583
     public function getHTTPDepth($default = self::DEPTH_INFINITY) {
Please login to merge, or discard this patch.
Braces   +110 added lines, -41 removed lines patch added patch discarded remove patch
@@ -326,8 +326,9 @@  discard block
 block discarded – undo
326 326
     public function setBaseUri($uri) {
327 327
 
328 328
         // If the baseUri does not end with a slash, we must add it
329
-        if ($uri[strlen($uri) - 1] !== '/')
330
-            $uri .= '/';
329
+        if ($uri[strlen($uri) - 1] !== '/') {
330
+                    $uri .= '/';
331
+        }
331 332
 
332 333
         $this->baseUri = $uri;
333 334
 
@@ -340,7 +341,9 @@  discard block
 block discarded – undo
340 341
      */
341 342
     public function getBaseUri() {
342 343
 
343
-        if (is_null($this->baseUri)) $this->baseUri = $this->guessBaseUri();
344
+        if (is_null($this->baseUri)) {
345
+        	$this->baseUri = $this->guessBaseUri();
346
+        }
344 347
         return $this->baseUri;
345 348
 
346 349
     }
@@ -362,8 +365,9 @@  discard block
 block discarded – undo
362 365
         if (!empty($pathInfo)) {
363 366
 
364 367
             // We need to make sure we ignore the QUERY_STRING part
365
-            if ($pos = strpos($uri, '?'))
366
-                $uri = substr($uri, 0, $pos);
368
+            if ($pos = strpos($uri, '?')) {
369
+                            $uri = substr($uri, 0, $pos);
370
+            }
367 371
 
368 372
             // PATH_INFO is only set for urls, such as: /example.php/path
369 373
             // in that case PATH_INFO contains '/path'.
@@ -412,8 +416,9 @@  discard block
 block discarded – undo
412 416
      */
413 417
     public function getPlugin($name) {
414 418
 
415
-        if (isset($this->plugins[$name]))
416
-            return $this->plugins[$name];
419
+        if (isset($this->plugins[$name])) {
420
+                    return $this->plugins[$name];
421
+        }
417 422
 
418 423
         return null;
419 424
 
@@ -442,8 +447,12 @@  discard block
 block discarded – undo
442 447
 
443 448
         $method = $request->getMethod();
444 449
 
445
-        if (!$this->emit('beforeMethod:' . $method, [$request, $response])) return;
446
-        if (!$this->emit('beforeMethod', [$request, $response])) return;
450
+        if (!$this->emit('beforeMethod:' . $method, [$request, $response])) {
451
+        	return;
452
+        }
453
+        if (!$this->emit('beforeMethod', [$request, $response])) {
454
+        	return;
455
+        }
447 456
 
448 457
         if (self::$exposeVersion) {
449 458
             $response->setHeader('X-Sabre-Version', Version::VERSION);
@@ -468,8 +477,12 @@  discard block
 block discarded – undo
468 477
             }
469 478
         }
470 479
 
471
-        if (!$this->emit('afterMethod:' . $method, [$request, $response])) return;
472
-        if (!$this->emit('afterMethod', [$request, $response])) return;
480
+        if (!$this->emit('afterMethod:' . $method, [$request, $response])) {
481
+        	return;
482
+        }
483
+        if (!$this->emit('afterMethod', [$request, $response])) {
484
+        	return;
485
+        }
473 486
 
474 487
         if ($response->getStatus() === null) {
475 488
             throw new Exception('No subsystem set a valid HTTP status code. Something must have interrupted the request without providing further detail.');
@@ -512,7 +525,9 @@  discard block
 block discarded – undo
512 525
         }
513 526
 
514 527
         // We're also checking if any of the plugins register any new methods
515
-        foreach ($this->plugins as $plugin) $methods = array_merge($methods, $plugin->getHTTPMethods($path));
528
+        foreach ($this->plugins as $plugin) {
529
+        	$methods = array_merge($methods, $plugin->getHTTPMethods($path));
530
+        }
516 531
         array_unique($methods);
517 532
 
518 533
         return $methods;
@@ -585,13 +600,19 @@  discard block
 block discarded – undo
585 600
         // If its not set, we'll grab the default
586 601
         $depth = $this->httpRequest->getHeader('Depth');
587 602
 
588
-        if (is_null($depth)) return $default;
603
+        if (is_null($depth)) {
604
+        	return $default;
605
+        }
589 606
 
590
-        if ($depth == 'infinity') return self::DEPTH_INFINITY;
607
+        if ($depth == 'infinity') {
608
+        	return self::DEPTH_INFINITY;
609
+        }
591 610
 
592 611
 
593 612
         // If its an unknown value. we'll grab the default
594
-        if (!ctype_digit($depth)) return $default;
613
+        if (!ctype_digit($depth)) {
614
+        	return $default;
615
+        }
595 616
 
596 617
         return (int)$depth;
597 618
 
@@ -614,13 +635,19 @@  discard block
 block discarded – undo
614 635
     public function getHTTPRange() {
615 636
 
616 637
         $range = $this->httpRequest->getHeader('range');
617
-        if (is_null($range)) return null;
638
+        if (is_null($range)) {
639
+        	return null;
640
+        }
618 641
 
619 642
         // Matching "Range: bytes=1234-5678: both numbers are optional
620 643
 
621
-        if (!preg_match('/^bytes=([0-9]*)-([0-9]*)$/i', $range, $matches)) return null;
644
+        if (!preg_match('/^bytes=([0-9]*)-([0-9]*)$/i', $range, $matches)) {
645
+        	return null;
646
+        }
622 647
 
623
-        if ($matches[1] === '' && $matches[2] === '') return null;
648
+        if ($matches[1] === '' && $matches[2] === '') {
649
+        	return null;
650
+        }
624 651
 
625 652
         return [
626 653
             $matches[1] !== '' ? $matches[1] : null,
@@ -709,20 +736,31 @@  discard block
 block discarded – undo
709 736
     public function getCopyAndMoveInfo(RequestInterface $request) {
710 737
 
711 738
         // Collecting the relevant HTTP headers
712
-        if (!$request->getHeader('Destination')) throw new Exception\BadRequest('The destination header was not supplied');
739
+        if (!$request->getHeader('Destination')) {
740
+        	throw new Exception\BadRequest('The destination header was not supplied');
741
+        }
713 742
         $destination = $this->calculateUri($request->getHeader('Destination'));
714 743
         $overwrite = $request->getHeader('Overwrite');
715
-        if (!$overwrite) $overwrite = 'T';
716
-        if (strtoupper($overwrite) == 'T') $overwrite = true;
717
-        elseif (strtoupper($overwrite) == 'F') $overwrite = false;
744
+        if (!$overwrite) {
745
+        	$overwrite = 'T';
746
+        }
747
+        if (strtoupper($overwrite) == 'T') {
748
+        	$overwrite = true;
749
+        } elseif (strtoupper($overwrite) == 'F') {
750
+        	$overwrite = false;
751
+        }
718 752
         // We need to throw a bad request exception, if the header was invalid
719
-        else throw new Exception\BadRequest('The HTTP Overwrite header should be either T or F');
753
+        else {
754
+        	throw new Exception\BadRequest('The HTTP Overwrite header should be either T or F');
755
+        }
720 756
 
721 757
         list($destinationDir) = URLUtil::splitPath($destination);
722 758
 
723 759
         try {
724 760
             $destinationParent = $this->tree->getNodeForPath($destinationDir);
725
-            if (!($destinationParent instanceof ICollection)) throw new Exception\UnsupportedMediaType('The destination node is not a collection');
761
+            if (!($destinationParent instanceof ICollection)) {
762
+            	throw new Exception\UnsupportedMediaType('The destination node is not a collection');
763
+            }
726 764
         } catch (Exception\NotFound $e) {
727 765
 
728 766
             // If the destination parent node is not found, we throw a 409
@@ -735,7 +773,9 @@  discard block
 block discarded – undo
735 773
 
736 774
             // If this succeeded, it means the destination already exists
737 775
             // we'll need to throw precondition failed in case overwrite is false
738
-            if (!$overwrite) throw new Exception\PreconditionFailed('The destination node already exists, and the overwrite header is set to false', 'Overwrite');
776
+            if (!$overwrite) {
777
+            	throw new Exception\PreconditionFailed('The destination node already exists, and the overwrite header is set to false', 'Overwrite');
778
+            }
739 779
 
740 780
         } catch (Exception\NotFound $e) {
741 781
 
@@ -804,7 +844,9 @@  discard block
 block discarded – undo
804 844
         foreach ($this->getPropertiesForPath($path, $propertyNames, 1) as $k => $row) {
805 845
 
806 846
             // Skipping the parent path
807
-            if ($k === 0) continue;
847
+            if ($k === 0) {
848
+            	continue;
849
+            }
808 850
 
809 851
             $result[$row['href']] = $row[200];
810 852
 
@@ -838,7 +880,9 @@  discard block
 block discarded – undo
838 880
 
839 881
         $headers = [];
840 882
         foreach ($propertyMap as $property => $header) {
841
-            if (!isset($properties[$property])) continue;
883
+            if (!isset($properties[$property])) {
884
+            	continue;
885
+            }
842 886
 
843 887
             if (is_scalar($properties[$property])) {
844 888
                 $headers[$header] = $properties[$property];
@@ -909,7 +953,9 @@  discard block
 block discarded – undo
909 953
     public function getPropertiesForPath($path, $propertyNames = [], $depth = 0) {
910 954
 
911 955
         // The only two options for the depth of a propfind is 0 or 1 - as long as depth infinity is not enabled
912
-        if (!$this->enablePropfindDepthInfinity && $depth != 0) $depth = 1;
956
+        if (!$this->enablePropfindDepthInfinity && $depth != 0) {
957
+        	$depth = 1;
958
+        }
913 959
 
914 960
         $path = trim($path, '/');
915 961
 
@@ -1034,7 +1080,9 @@  discard block
 block discarded – undo
1034 1080
 
1035 1081
         list($dir, $name) = URLUtil::splitPath($uri);
1036 1082
 
1037
-        if (!$this->emit('beforeBind', [$uri])) return false;
1083
+        if (!$this->emit('beforeBind', [$uri])) {
1084
+        	return false;
1085
+        }
1038 1086
 
1039 1087
         $parent = $this->tree->getNodeForPath($dir);
1040 1088
         if (!$parent instanceof ICollection) {
@@ -1047,11 +1095,15 @@  discard block
 block discarded – undo
1047 1095
         //
1048 1096
         // If $modified is true, we must not send back an ETag.
1049 1097
         $modified = false;
1050
-        if (!$this->emit('beforeCreateFile', [$uri, &$data, $parent, &$modified])) return false;
1098
+        if (!$this->emit('beforeCreateFile', [$uri, &$data, $parent, &$modified])) {
1099
+        	return false;
1100
+        }
1051 1101
 
1052 1102
         $etag = $parent->createFile($name, $data);
1053 1103
 
1054
-        if ($modified) $etag = null;
1104
+        if ($modified) {
1105
+        	$etag = null;
1106
+        }
1055 1107
 
1056 1108
         $this->tree->markDirty($dir . '/' . $name);
1057 1109
 
@@ -1081,10 +1133,14 @@  discard block
 block discarded – undo
1081 1133
         //
1082 1134
         // If $modified is true, we must not send back an ETag.
1083 1135
         $modified = false;
1084
-        if (!$this->emit('beforeWriteContent', [$uri, $node, &$data, &$modified])) return false;
1136
+        if (!$this->emit('beforeWriteContent', [$uri, $node, &$data, &$modified])) {
1137
+        	return false;
1138
+        }
1085 1139
 
1086 1140
         $etag = $node->put($data);
1087
-        if ($modified) $etag = null;
1141
+        if ($modified) {
1142
+        	$etag = null;
1143
+        }
1088 1144
         $this->emit('afterWriteContent', [$uri, $node]);
1089 1145
 
1090 1146
         return true;
@@ -1141,7 +1197,9 @@  discard block
 block discarded – undo
1141 1197
         }
1142 1198
 
1143 1199
 
1144
-        if (!$this->emit('beforeBind', [$uri])) return;
1200
+        if (!$this->emit('beforeBind', [$uri])) {
1201
+        	return;
1202
+        }
1145 1203
 
1146 1204
         if ($parent instanceof IExtendedCollection) {
1147 1205
 
@@ -1279,7 +1337,9 @@  discard block
 block discarded – undo
1279 1337
 
1280 1338
                 }
1281 1339
                 if (!$haveMatch) {
1282
-                    if ($etag) $response->setHeader('ETag', $etag);
1340
+                    if ($etag) {
1341
+                    	$response->setHeader('ETag', $etag);
1342
+                    }
1283 1343
                      throw new Exception\PreconditionFailed('An If-Match header was specified, but none of the specified the ETags matched.', 'If-Match');
1284 1344
                 }
1285 1345
             }
@@ -1301,8 +1361,9 @@  discard block
 block discarded – undo
1301 1361
             }
1302 1362
             if ($nodeExists) {
1303 1363
                 $haveMatch = false;
1304
-                if ($ifNoneMatch === '*') $haveMatch = true;
1305
-                else {
1364
+                if ($ifNoneMatch === '*') {
1365
+                	$haveMatch = true;
1366
+                } else {
1306 1367
 
1307 1368
                     // There might be multiple ETags
1308 1369
                     $ifNoneMatch = explode(',', $ifNoneMatch);
@@ -1313,14 +1374,18 @@  discard block
 block discarded – undo
1313 1374
                         // Stripping any extra spaces
1314 1375
                         $ifNoneMatchItem = trim($ifNoneMatchItem, ' ');
1315 1376
 
1316
-                        if ($etag === $ifNoneMatchItem) $haveMatch = true;
1377
+                        if ($etag === $ifNoneMatchItem) {
1378
+                        	$haveMatch = true;
1379
+                        }
1317 1380
 
1318 1381
                     }
1319 1382
 
1320 1383
                 }
1321 1384
 
1322 1385
                 if ($haveMatch) {
1323
-                    if ($etag) $response->setHeader('ETag', $etag);
1386
+                    if ($etag) {
1387
+                    	$response->setHeader('ETag', $etag);
1388
+                    }
1324 1389
                     if ($request->getMethod() === 'GET') {
1325 1390
                         $response->setStatus(304);
1326 1391
                         return false;
@@ -1523,7 +1588,9 @@  discard block
 block discarded – undo
1523 1588
     public function getIfConditions(RequestInterface $request) {
1524 1589
 
1525 1590
         $header = $request->getHeader('If');
1526
-        if (!$header) return [];
1591
+        if (!$header) {
1592
+        	return [];
1593
+        }
1527 1594
 
1528 1595
         $matches = [];
1529 1596
 
@@ -1580,7 +1647,9 @@  discard block
 block discarded – undo
1580 1647
 
1581 1648
         $result = [];
1582 1649
         foreach ($this->resourceTypeMapping as $className => $resourceType) {
1583
-            if ($node instanceof $className) $result[] = $resourceType;
1650
+            if ($node instanceof $className) {
1651
+            	$result[] = $resourceType;
1652
+            }
1584 1653
         }
1585 1654
         return $result;
1586 1655
 
Please login to merge, or discard this patch.
libraries/SabreDAV/DAV/CorePlugin.php 4 patches
Braces   +71 added lines, -27 removed lines patch added patch discarded remove patch
@@ -78,7 +78,9 @@  discard block
 block discarded – undo
78 78
         $path = $request->getPath();
79 79
         $node = $this->server->tree->getNodeForPath($path);
80 80
 
81
-        if (!$node instanceof IFile) return;
81
+        if (!$node instanceof IFile) {
82
+        	return;
83
+        }
82 84
 
83 85
         $body = $node->get();
84 86
 
@@ -132,17 +134,23 @@  discard block
 block discarded – undo
132 134
 
133 135
                 // It's a date. We must check if the entity is modified since
134 136
                 // the specified date.
135
-                if (!isset($httpHeaders['Last-Modified'])) $ignoreRangeHeader = true;
136
-                else {
137
+                if (!isset($httpHeaders['Last-Modified'])) {
138
+                	$ignoreRangeHeader = true;
139
+                } else {
137 140
                     $modified = new \DateTime($httpHeaders['Last-Modified']);
138
-                    if ($modified > $ifRangeDate) $ignoreRangeHeader = true;
141
+                    if ($modified > $ifRangeDate) {
142
+                    	$ignoreRangeHeader = true;
143
+                    }
139 144
                 }
140 145
 
141 146
             } catch (\Exception $e) {
142 147
 
143 148
                 // It's an entity. We can do a simple comparison.
144
-                if (!isset($httpHeaders['ETag'])) $ignoreRangeHeader = true;
145
-                elseif ($httpHeaders['ETag'] !== $ifRange) $ignoreRangeHeader = true;
149
+                if (!isset($httpHeaders['ETag'])) {
150
+                	$ignoreRangeHeader = true;
151
+                } elseif ($httpHeaders['ETag'] !== $ifRange) {
152
+                	$ignoreRangeHeader = true;
153
+                }
146 154
             }
147 155
         }
148 156
 
@@ -154,18 +162,25 @@  discard block
 block discarded – undo
154 162
 
155 163
                 $start = $range[0];
156 164
                 $end = $range[1] ? $range[1] : $nodeSize - 1;
157
-                if ($start >= $nodeSize)
158
-                    throw new Exception\RequestedRangeNotSatisfiable('The start offset (' . $range[0] . ') exceeded the size of the entity (' . $nodeSize . ')');
165
+                if ($start >= $nodeSize) {
166
+                                    throw new Exception\RequestedRangeNotSatisfiable('The start offset (' . $range[0] . ') exceeded the size of the entity (' . $nodeSize . ')');
167
+                }
159 168
 
160
-                if ($end < $start) throw new Exception\RequestedRangeNotSatisfiable('The end offset (' . $range[1] . ') is lower than the start offset (' . $range[0] . ')');
161
-                if ($end >= $nodeSize) $end = $nodeSize - 1;
169
+                if ($end < $start) {
170
+                	throw new Exception\RequestedRangeNotSatisfiable('The end offset (' . $range[1] . ') is lower than the start offset (' . $range[0] . ')');
171
+                }
172
+                if ($end >= $nodeSize) {
173
+                	$end = $nodeSize - 1;
174
+                }
162 175
 
163 176
             } else {
164 177
 
165 178
                 $start = $nodeSize - $range[1];
166 179
                 $end  = $nodeSize - 1;
167 180
 
168
-                if ($start < 0) $start = 0;
181
+                if ($start < 0) {
182
+                	$start = 0;
183
+                }
169 184
 
170 185
             }
171 186
 
@@ -175,7 +190,9 @@  discard block
 block discarded – undo
175 190
             if (!stream_get_meta_data($body)['seekable'] || fseek($body, $start, SEEK_SET) === -1) {
176 191
                 $consumeBlock = 8192;
177 192
                 for ($consumed = 0; $start - $consumed > 0;){
178
-                    if (feof($body)) throw new Exception\RequestedRangeNotSatisfiable('The start offset (' . $start . ') exceeded the size of the entity (' . $consumed . ')');
193
+                    if (feof($body)) {
194
+                    	throw new Exception\RequestedRangeNotSatisfiable('The start offset (' . $start . ') exceeded the size of the entity (' . $consumed . ')');
195
+                    }
179 196
                     $consumed += strlen(fread($body, min($start - $consumed, $consumeBlock)));
180 197
                 }
181 198
             }
@@ -187,7 +204,9 @@  discard block
 block discarded – undo
187 204
 
188 205
         } else {
189 206
 
190
-            if ($nodeSize) $response->setHeader('Content-Length', $nodeSize);
207
+            if ($nodeSize) {
208
+            	$response->setHeader('Content-Length', $nodeSize);
209
+            }
191 210
             $response->setStatus(200);
192 211
             $response->setBody($body);
193 212
 
@@ -281,7 +300,9 @@  discard block
 block discarded – undo
281 300
 
282 301
         $path = $request->getPath();
283 302
 
284
-        if (!$this->server->emit('beforeUnbind', [$path])) return false;
303
+        if (!$this->server->emit('beforeUnbind', [$path])) {
304
+        	return false;
305
+        }
285 306
         $this->server->tree->delete($path);
286 307
         $this->server->emit('afterUnbind', [$path]);
287 308
 
@@ -329,7 +350,9 @@  discard block
 block discarded – undo
329 350
 
330 351
         $depth = $this->server->getHTTPDepth(1);
331 352
         // The only two options for the depth of a propfind is 0 or 1 - as long as depth infinity is not enabled
332
-        if (!$this->server->enablePropfindDepthInfinity && $depth != 0) $depth = 1;
353
+        if (!$this->server->enablePropfindDepthInfinity && $depth != 0) {
354
+        	$depth = 1;
355
+        }
333 356
 
334 357
         $newProperties = $this->server->getPropertiesForPath($path, $propFindXml->properties, $depth);
335 358
 
@@ -506,14 +529,18 @@  discard block
 block discarded – undo
506 529
             $node = $this->server->tree->getNodeForPath($path);
507 530
 
508 531
             // If the node is a collection, we'll deny it
509
-            if (!($node instanceof IFile)) throw new Exception\Conflict('PUT is not allowed on non-files.');
532
+            if (!($node instanceof IFile)) {
533
+            	throw new Exception\Conflict('PUT is not allowed on non-files.');
534
+            }
510 535
 
511 536
             if (!$this->server->updateFile($path, $body, $etag)) {
512 537
                 return false;
513 538
             }
514 539
 
515 540
             $response->setHeader('Content-Length', '0');
516
-            if ($etag) $response->setHeader('ETag', $etag);
541
+            if ($etag) {
542
+            	$response->setHeader('ETag', $etag);
543
+            }
517 544
             $response->setStatus(204);
518 545
 
519 546
         } else {
@@ -526,7 +553,9 @@  discard block
 block discarded – undo
526 553
             }
527 554
 
528 555
             $response->setHeader('Content-Length', '0');
529
-            if ($etag) $response->setHeader('ETag', $etag);
556
+            if ($etag) {
557
+            	$response->setHeader('ETag', $etag);
558
+            }
530 559
             $response->setStatus(201);
531 560
 
532 561
         }
@@ -570,8 +599,9 @@  discard block
 block discarded – undo
570 599
 
571 600
             $properties = $mkcol->getProperties();
572 601
 
573
-            if (!isset($properties['{DAV:}resourcetype']))
574
-                throw new Exception\BadRequest('The mkcol request must include a {DAV:}resourcetype property');
602
+            if (!isset($properties['{DAV:}resourcetype'])) {
603
+                            throw new Exception\BadRequest('The mkcol request must include a {DAV:}resourcetype property');
604
+            }
575 605
 
576 606
             $resourceType = $properties['{DAV:}resourcetype']->getValue();
577 607
             unset($properties['{DAV:}resourcetype']);
@@ -623,12 +653,20 @@  discard block
 block discarded – undo
623 653
 
624 654
         if ($moveInfo['destinationExists']) {
625 655
 
626
-            if (!$this->server->emit('beforeUnbind', [$moveInfo['destination']])) return false;
656
+            if (!$this->server->emit('beforeUnbind', [$moveInfo['destination']])) {
657
+            	return false;
658
+            }
627 659
 
628 660
         }
629
-        if (!$this->server->emit('beforeUnbind', [$path])) return false;
630
-        if (!$this->server->emit('beforeBind', [$moveInfo['destination']])) return false;
631
-        if (!$this->server->emit('beforeMove', [$path, $moveInfo['destination']])) return false;
661
+        if (!$this->server->emit('beforeUnbind', [$path])) {
662
+        	return false;
663
+        }
664
+        if (!$this->server->emit('beforeBind', [$moveInfo['destination']])) {
665
+        	return false;
666
+        }
667
+        if (!$this->server->emit('beforeMove', [$path, $moveInfo['destination']])) {
668
+        	return false;
669
+        }
632 670
 
633 671
         if ($moveInfo['destinationExists']) {
634 672
 
@@ -673,9 +711,13 @@  discard block
 block discarded – undo
673 711
 
674 712
         $copyInfo = $this->server->getCopyAndMoveInfo($request);
675 713
 
676
-        if (!$this->server->emit('beforeBind', [$copyInfo['destination']])) return false;
714
+        if (!$this->server->emit('beforeBind', [$copyInfo['destination']])) {
715
+        	return false;
716
+        }
677 717
         if ($copyInfo['destinationExists']) {
678
-            if (!$this->server->emit('beforeUnbind', [$copyInfo['destination']])) return false;
718
+            if (!$this->server->emit('beforeUnbind', [$copyInfo['destination']])) {
719
+            	return false;
720
+            }
679 721
             $this->server->tree->delete($copyInfo['destination']);
680 722
         }
681 723
 
@@ -871,7 +913,9 @@  discard block
 block discarded – undo
871 913
             // If we already have a sync-token from the current propFind
872 914
             // request, we can re-use that.
873 915
             $val = $propFind->get('{http://sabredav.org/ns}sync-token');
874
-            if ($val) return $val;
916
+            if ($val) {
917
+            	return $val;
918
+            }
875 919
 
876 920
             $val = $propFind->get('{DAV:}sync-token');
877 921
             if ($val && is_scalar($val)) {
Please login to merge, or discard this patch.
Indentation   +809 added lines, -809 removed lines patch added patch discarded remove patch
@@ -16,449 +16,449 @@  discard block
 block discarded – undo
16 16
  */
17 17
 class CorePlugin extends ServerPlugin {
18 18
 
19
-    /**
20
-     * Reference to server object.
21
-     *
22
-     * @var Server
23
-     */
24
-    protected $server;
25
-
26
-    /**
27
-     * Sets up the plugin
28
-     *
29
-     * @param Server $server
30
-     * @return void
31
-     */
32
-    public function initialize(Server $server) {
33
-
34
-        $this->server = $server;
35
-        $server->on('method:GET',       [$this, 'httpGet']);
36
-        $server->on('method:OPTIONS',   [$this, 'httpOptions']);
37
-        $server->on('method:HEAD',      [$this, 'httpHead']);
38
-        $server->on('method:DELETE',    [$this, 'httpDelete']);
39
-        $server->on('method:PROPFIND',  [$this, 'httpPropFind']);
40
-        $server->on('method:PROPPATCH', [$this, 'httpPropPatch']);
41
-        $server->on('method:PUT',       [$this, 'httpPut']);
42
-        $server->on('method:MKCOL',     [$this, 'httpMkcol']);
43
-        $server->on('method:MOVE',      [$this, 'httpMove']);
44
-        $server->on('method:COPY',      [$this, 'httpCopy']);
45
-        $server->on('method:REPORT',    [$this, 'httpReport']);
46
-
47
-        $server->on('propPatch',        [$this, 'propPatchProtectedPropertyCheck'], 90);
48
-        $server->on('propPatch',        [$this, 'propPatchNodeUpdate'], 200);
49
-        $server->on('propFind',         [$this, 'propFind']);
50
-        $server->on('propFind',         [$this, 'propFindNode'], 120);
51
-        $server->on('propFind',         [$this, 'propFindLate'], 200);
52
-
53
-    }
54
-
55
-    /**
56
-     * Returns a plugin name.
57
-     *
58
-     * Using this name other plugins will be able to access other plugins
59
-     * using DAV\Server::getPlugin
60
-     *
61
-     * @return string
62
-     */
63
-    public function getPluginName() {
64
-
65
-        return 'core';
66
-
67
-    }
68
-
69
-    /**
70
-     * This is the default implementation for the GET method.
71
-     *
72
-     * @param RequestInterface $request
73
-     * @param ResponseInterface $response
74
-     * @return bool
75
-     */
76
-    public function httpGet(RequestInterface $request, ResponseInterface $response) {
77
-
78
-        $path = $request->getPath();
79
-        $node = $this->server->tree->getNodeForPath($path);
80
-
81
-        if (!$node instanceof IFile) return;
82
-
83
-        $body = $node->get();
84
-
85
-        // Converting string into stream, if needed.
86
-        if (is_string($body)) {
87
-            $stream = fopen('php://temp', 'r+');
88
-            fwrite($stream, $body);
89
-            rewind($stream);
90
-            $body = $stream;
91
-        }
92
-
93
-        $httpHeaders = $this->server->getHTTPHeaders($path);
94
-
95
-        /* ContentType needs to get a default, because many webservers will otherwise
19
+	/**
20
+	 * Reference to server object.
21
+	 *
22
+	 * @var Server
23
+	 */
24
+	protected $server;
25
+
26
+	/**
27
+	 * Sets up the plugin
28
+	 *
29
+	 * @param Server $server
30
+	 * @return void
31
+	 */
32
+	public function initialize(Server $server) {
33
+
34
+		$this->server = $server;
35
+		$server->on('method:GET',       [$this, 'httpGet']);
36
+		$server->on('method:OPTIONS',   [$this, 'httpOptions']);
37
+		$server->on('method:HEAD',      [$this, 'httpHead']);
38
+		$server->on('method:DELETE',    [$this, 'httpDelete']);
39
+		$server->on('method:PROPFIND',  [$this, 'httpPropFind']);
40
+		$server->on('method:PROPPATCH', [$this, 'httpPropPatch']);
41
+		$server->on('method:PUT',       [$this, 'httpPut']);
42
+		$server->on('method:MKCOL',     [$this, 'httpMkcol']);
43
+		$server->on('method:MOVE',      [$this, 'httpMove']);
44
+		$server->on('method:COPY',      [$this, 'httpCopy']);
45
+		$server->on('method:REPORT',    [$this, 'httpReport']);
46
+
47
+		$server->on('propPatch',        [$this, 'propPatchProtectedPropertyCheck'], 90);
48
+		$server->on('propPatch',        [$this, 'propPatchNodeUpdate'], 200);
49
+		$server->on('propFind',         [$this, 'propFind']);
50
+		$server->on('propFind',         [$this, 'propFindNode'], 120);
51
+		$server->on('propFind',         [$this, 'propFindLate'], 200);
52
+
53
+	}
54
+
55
+	/**
56
+	 * Returns a plugin name.
57
+	 *
58
+	 * Using this name other plugins will be able to access other plugins
59
+	 * using DAV\Server::getPlugin
60
+	 *
61
+	 * @return string
62
+	 */
63
+	public function getPluginName() {
64
+
65
+		return 'core';
66
+
67
+	}
68
+
69
+	/**
70
+	 * This is the default implementation for the GET method.
71
+	 *
72
+	 * @param RequestInterface $request
73
+	 * @param ResponseInterface $response
74
+	 * @return bool
75
+	 */
76
+	public function httpGet(RequestInterface $request, ResponseInterface $response) {
77
+
78
+		$path = $request->getPath();
79
+		$node = $this->server->tree->getNodeForPath($path);
80
+
81
+		if (!$node instanceof IFile) return;
82
+
83
+		$body = $node->get();
84
+
85
+		// Converting string into stream, if needed.
86
+		if (is_string($body)) {
87
+			$stream = fopen('php://temp', 'r+');
88
+			fwrite($stream, $body);
89
+			rewind($stream);
90
+			$body = $stream;
91
+		}
92
+
93
+		$httpHeaders = $this->server->getHTTPHeaders($path);
94
+
95
+		/* ContentType needs to get a default, because many webservers will otherwise
96 96
          * default to text/html, and we don't want this for security reasons.
97 97
          */
98
-        if (!isset($httpHeaders['Content-Type'])) {
99
-            $httpHeaders['Content-Type'] = 'application/octet-stream';
100
-        }
98
+		if (!isset($httpHeaders['Content-Type'])) {
99
+			$httpHeaders['Content-Type'] = 'application/octet-stream';
100
+		}
101 101
 
102 102
 
103
-        if (isset($httpHeaders['Content-Length'])) {
103
+		if (isset($httpHeaders['Content-Length'])) {
104 104
 
105
-            $nodeSize = $httpHeaders['Content-Length'];
105
+			$nodeSize = $httpHeaders['Content-Length'];
106 106
 
107
-            // Need to unset Content-Length, because we'll handle that during figuring out the range
108
-            unset($httpHeaders['Content-Length']);
107
+			// Need to unset Content-Length, because we'll handle that during figuring out the range
108
+			unset($httpHeaders['Content-Length']);
109 109
 
110
-        } else {
111
-            $nodeSize = null;
112
-        }
110
+		} else {
111
+			$nodeSize = null;
112
+		}
113 113
 
114
-        $response->addHeaders($httpHeaders);
114
+		$response->addHeaders($httpHeaders);
115 115
 
116
-        $range = $this->server->getHTTPRange();
117
-        $ifRange = $request->getHeader('If-Range');
118
-        $ignoreRangeHeader = false;
116
+		$range = $this->server->getHTTPRange();
117
+		$ifRange = $request->getHeader('If-Range');
118
+		$ignoreRangeHeader = false;
119 119
 
120
-        // If ifRange is set, and range is specified, we first need to check
121
-        // the precondition.
122
-        if ($nodeSize && $range && $ifRange) {
120
+		// If ifRange is set, and range is specified, we first need to check
121
+		// the precondition.
122
+		if ($nodeSize && $range && $ifRange) {
123 123
 
124
-            // if IfRange is parsable as a date we'll treat it as a DateTime
125
-            // otherwise, we must treat it as an etag.
126
-            try {
127
-                $ifRangeDate = new \DateTime($ifRange);
124
+			// if IfRange is parsable as a date we'll treat it as a DateTime
125
+			// otherwise, we must treat it as an etag.
126
+			try {
127
+				$ifRangeDate = new \DateTime($ifRange);
128 128
 
129
-                // It's a date. We must check if the entity is modified since
130
-                // the specified date.
131
-                if (!isset($httpHeaders['Last-Modified'])) $ignoreRangeHeader = true;
132
-                else {
133
-                    $modified = new \DateTime($httpHeaders['Last-Modified']);
134
-                    if ($modified > $ifRangeDate) $ignoreRangeHeader = true;
135
-                }
129
+				// It's a date. We must check if the entity is modified since
130
+				// the specified date.
131
+				if (!isset($httpHeaders['Last-Modified'])) $ignoreRangeHeader = true;
132
+				else {
133
+					$modified = new \DateTime($httpHeaders['Last-Modified']);
134
+					if ($modified > $ifRangeDate) $ignoreRangeHeader = true;
135
+				}
136 136
 
137
-            } catch (\Exception $e) {
138
-
139
-                // It's an entity. We can do a simple comparison.
140
-                if (!isset($httpHeaders['ETag'])) $ignoreRangeHeader = true;
141
-                elseif ($httpHeaders['ETag'] !== $ifRange) $ignoreRangeHeader = true;
142
-            }
143
-        }
144
-
145
-        // We're only going to support HTTP ranges if the backend provided a filesize
146
-        if (!$ignoreRangeHeader && $nodeSize && $range) {
147
-
148
-            // Determining the exact byte offsets
149
-            if (!is_null($range[0])) {
150
-
151
-                $start = $range[0];
152
-                $end = $range[1] ? $range[1] : $nodeSize - 1;
153
-                if ($start >= $nodeSize)
154
-                    throw new Exception\RequestedRangeNotSatisfiable('The start offset (' . $range[0] . ') exceeded the size of the entity (' . $nodeSize . ')');
155
-
156
-                if ($end < $start) throw new Exception\RequestedRangeNotSatisfiable('The end offset (' . $range[1] . ') is lower than the start offset (' . $range[0] . ')');
157
-                if ($end >= $nodeSize) $end = $nodeSize - 1;
158
-
159
-            } else {
160
-
161
-                $start = $nodeSize - $range[1];
162
-                $end  = $nodeSize - 1;
163
-
164
-                if ($start < 0) $start = 0;
165
-
166
-            }
167
-
168
-            // Streams may advertise themselves as seekable, but still not
169
-            // actually allow fseek.  We'll manually go forward in the stream
170
-            // if fseek failed.
171
-            if (!stream_get_meta_data($body)['seekable'] || fseek($body, $start, SEEK_SET) === -1) {
172
-                $consumeBlock = 8192;
173
-                for ($consumed = 0; $start - $consumed > 0;){
174
-                    if (feof($body)) throw new Exception\RequestedRangeNotSatisfiable('The start offset (' . $start . ') exceeded the size of the entity (' . $consumed . ')');
175
-                    $consumed += strlen(fread($body, min($start - $consumed, $consumeBlock)));
176
-                }
177
-            }
178
-
179
-            $response->setHeader('Content-Length', $end - $start + 1);
180
-            $response->setHeader('Content-Range', 'bytes ' . $start . '-' . $end . '/' . $nodeSize);
181
-            $response->setStatus(206);
182
-            $response->setBody($body);
183
-
184
-        } else {
185
-
186
-            if ($nodeSize) $response->setHeader('Content-Length', $nodeSize);
187
-            $response->setStatus(200);
188
-            $response->setBody($body);
189
-
190
-        }
191
-        // Sending back false will interupt the event chain and tell the server
192
-        // we've handled this method.
193
-        return false;
194
-
195
-    }
196
-
197
-    /**
198
-     * HTTP OPTIONS
199
-     *
200
-     * @param RequestInterface $request
201
-     * @param ResponseInterface $response
202
-     * @return bool
203
-     */
204
-    public function httpOptions(RequestInterface $request, ResponseInterface $response) {
205
-
206
-        $methods = $this->server->getAllowedMethods($request->getPath());
207
-
208
-        $response->setHeader('Allow', strtoupper(implode(', ', $methods)));
209
-        $features = ['1', '3', 'extended-mkcol'];
210
-
211
-        foreach ($this->server->getPlugins() as $plugin) {
212
-            $features = array_merge($features, $plugin->getFeatures());
213
-        }
214
-
215
-        $response->setHeader('DAV', implode(', ', $features));
216
-        $response->setHeader('MS-Author-Via', 'DAV');
217
-        $response->setHeader('Accept-Ranges', 'bytes');
218
-        $response->setHeader('Content-Length', '0');
219
-        $response->setStatus(200);
220
-
221
-        // Sending back false will interupt the event chain and tell the server
222
-        // we've handled this method.
223
-        return false;
224
-
225
-    }
226
-
227
-    /**
228
-     * HTTP HEAD
229
-     *
230
-     * This method is normally used to take a peak at a url, and only get the
231
-     * HTTP response headers, without the body. This is used by clients to
232
-     * determine if a remote file was changed, so they can use a local cached
233
-     * version, instead of downloading it again
234
-     *
235
-     * @param RequestInterface $request
236
-     * @param ResponseInterface $response
237
-     * @return bool
238
-     */
239
-    public function httpHead(RequestInterface $request, ResponseInterface $response) {
240
-
241
-        // This is implemented by changing the HEAD request to a GET request,
242
-        // and dropping the response body.
243
-        $subRequest = clone $request;
244
-        $subRequest->setMethod('GET');
245
-
246
-        try {
247
-            $this->server->invokeMethod($subRequest, $response, false);
248
-            $response->setBody('');
249
-        } catch (Exception\NotImplemented $e) {
250
-            // Some clients may do HEAD requests on collections, however, GET
251
-            // requests and HEAD requests _may_ not be defined on a collection,
252
-            // which would trigger a 501.
253
-            // This breaks some clients though, so we're transforming these
254
-            // 501s into 200s.
255
-            $response->setStatus(200);
256
-            $response->setBody('');
257
-            $response->setHeader('Content-Type', 'text/plain');
258
-            $response->setHeader('X-Sabre-Real-Status', $e->getHTTPCode());
259
-        }
260
-
261
-        // Sending back false will interupt the event chain and tell the server
262
-        // we've handled this method.
263
-        return false;
264
-
265
-    }
266
-
267
-    /**
268
-     * HTTP Delete
269
-     *
270
-     * The HTTP delete method, deletes a given uri
271
-     *
272
-     * @param RequestInterface $request
273
-     * @param ResponseInterface $response
274
-     * @return void
275
-     */
276
-    public function httpDelete(RequestInterface $request, ResponseInterface $response) {
277
-
278
-        $path = $request->getPath();
279
-
280
-        if (!$this->server->emit('beforeUnbind', [$path])) return false;
281
-        $this->server->tree->delete($path);
282
-        $this->server->emit('afterUnbind', [$path]);
283
-
284
-        $response->setStatus(204);
285
-        $response->setHeader('Content-Length', '0');
286
-
287
-        // Sending back false will interupt the event chain and tell the server
288
-        // we've handled this method.
289
-        return false;
290
-
291
-    }
292
-
293
-    /**
294
-     * WebDAV PROPFIND
295
-     *
296
-     * This WebDAV method requests information about an uri resource, or a list of resources
297
-     * If a client wants to receive the properties for a single resource it will add an HTTP Depth: header with a 0 value
298
-     * If the value is 1, it means that it also expects a list of sub-resources (e.g.: files in a directory)
299
-     *
300
-     * The request body contains an XML data structure that has a list of properties the client understands
301
-     * The response body is also an xml document, containing information about every uri resource and the requested properties
302
-     *
303
-     * It has to return a HTTP 207 Multi-status status code
304
-     *
305
-     * @param RequestInterface $request
306
-     * @param ResponseInterface $response
307
-     * @return void
308
-     */
309
-    public function httpPropFind(RequestInterface $request, ResponseInterface $response) {
310
-
311
-        $path = $request->getPath();
312
-
313
-        $requestBody = $request->getBodyAsString();
314
-        if (strlen($requestBody)) {
315
-            try {
316
-                $propFindXml = $this->server->xml->expect('{DAV:}propfind', $requestBody);
317
-            } catch (ParseException $e) {
318
-                throw new BadRequest($e->getMessage(), null, $e);
319
-            }
320
-        } else {
321
-            $propFindXml = new Xml\Request\PropFind();
322
-            $propFindXml->allProp = true;
323
-            $propFindXml->properties = [];
324
-        }
325
-
326
-        $depth = $this->server->getHTTPDepth(1);
327
-        // The only two options for the depth of a propfind is 0 or 1 - as long as depth infinity is not enabled
328
-        if (!$this->server->enablePropfindDepthInfinity && $depth != 0) $depth = 1;
329
-
330
-        $newProperties = $this->server->getPropertiesForPath($path, $propFindXml->properties, $depth);
331
-
332
-        // This is a multi-status response
333
-        $response->setStatus(207);
334
-        $response->setHeader('Content-Type', 'application/xml; charset=utf-8');
335
-        $response->setHeader('Vary', 'Brief,Prefer');
336
-
337
-        // Normally this header is only needed for OPTIONS responses, however..
338
-        // iCal seems to also depend on these being set for PROPFIND. Since
339
-        // this is not harmful, we'll add it.
340
-        $features = ['1', '3', 'extended-mkcol'];
341
-        foreach ($this->server->getPlugins() as $plugin) {
342
-            $features = array_merge($features, $plugin->getFeatures());
343
-        }
344
-        $response->setHeader('DAV', implode(', ', $features));
345
-
346
-        $prefer = $this->server->getHTTPPrefer();
347
-        $minimal = $prefer['return'] === 'minimal';
348
-
349
-        $data = $this->server->generateMultiStatus($newProperties, $minimal);
350
-        $response->setBody($data);
351
-
352
-        // Sending back false will interupt the event chain and tell the server
353
-        // we've handled this method.
354
-        return false;
355
-
356
-    }
357
-
358
-    /**
359
-     * WebDAV PROPPATCH
360
-     *
361
-     * This method is called to update properties on a Node. The request is an XML body with all the mutations.
362
-     * In this XML body it is specified which properties should be set/updated and/or deleted
363
-     *
364
-     * @param RequestInterface $request
365
-     * @param ResponseInterface $response
366
-     * @return bool
367
-     */
368
-    public function httpPropPatch(RequestInterface $request, ResponseInterface $response) {
369
-
370
-        $path = $request->getPath();
371
-
372
-        try {
373
-            $propPatch = $this->server->xml->expect('{DAV:}propertyupdate', $request->getBody());
374
-        } catch (ParseException $e) {
375
-            throw new BadRequest($e->getMessage(), null, $e);
376
-        }
377
-        $newProperties = $propPatch->properties;
378
-
379
-        $result = $this->server->updateProperties($path, $newProperties);
380
-
381
-        $prefer = $this->server->getHTTPPrefer();
382
-        $response->setHeader('Vary', 'Brief,Prefer');
383
-
384
-        if ($prefer['return'] === 'minimal') {
385
-
386
-            // If return-minimal is specified, we only have to check if the
387
-            // request was succesful, and don't need to return the
388
-            // multi-status.
389
-            $ok = true;
390
-            foreach ($result as $prop => $code) {
391
-                if ((int)$code > 299) {
392
-                    $ok = false;
393
-                }
394
-            }
395
-
396
-            if ($ok) {
397
-
398
-                $response->setStatus(204);
399
-                return false;
400
-
401
-            }
402
-
403
-        }
404
-
405
-        $response->setStatus(207);
406
-        $response->setHeader('Content-Type', 'application/xml; charset=utf-8');
407
-
408
-
409
-        // Reorganizing the result for generateMultiStatus
410
-        $multiStatus = [];
411
-        foreach ($result as $propertyName => $code) {
412
-            if (isset($multiStatus[$code])) {
413
-                $multiStatus[$code][$propertyName] = null;
414
-            } else {
415
-                $multiStatus[$code] = [$propertyName => null];
416
-            }
417
-        }
418
-        $multiStatus['href'] = $path;
419
-
420
-        $response->setBody(
421
-            $this->server->generateMultiStatus([$multiStatus])
422
-        );
423
-
424
-        // Sending back false will interupt the event chain and tell the server
425
-        // we've handled this method.
426
-        return false;
427
-
428
-    }
429
-
430
-    /**
431
-     * HTTP PUT method
432
-     *
433
-     * This HTTP method updates a file, or creates a new one.
434
-     *
435
-     * If a new resource was created, a 201 Created status code should be returned. If an existing resource is updated, it's a 204 No Content
436
-     *
437
-     * @param RequestInterface $request
438
-     * @param ResponseInterface $response
439
-     * @return bool
440
-     */
441
-    public function httpPut(RequestInterface $request, ResponseInterface $response) {
442
-
443
-        $body = $request->getBodyAsStream();
444
-        $path = $request->getPath();
445
-
446
-        // Intercepting Content-Range
447
-        if ($request->getHeader('Content-Range')) {
448
-            /*
137
+			} catch (\Exception $e) {
138
+
139
+				// It's an entity. We can do a simple comparison.
140
+				if (!isset($httpHeaders['ETag'])) $ignoreRangeHeader = true;
141
+				elseif ($httpHeaders['ETag'] !== $ifRange) $ignoreRangeHeader = true;
142
+			}
143
+		}
144
+
145
+		// We're only going to support HTTP ranges if the backend provided a filesize
146
+		if (!$ignoreRangeHeader && $nodeSize && $range) {
147
+
148
+			// Determining the exact byte offsets
149
+			if (!is_null($range[0])) {
150
+
151
+				$start = $range[0];
152
+				$end = $range[1] ? $range[1] : $nodeSize - 1;
153
+				if ($start >= $nodeSize)
154
+					throw new Exception\RequestedRangeNotSatisfiable('The start offset (' . $range[0] . ') exceeded the size of the entity (' . $nodeSize . ')');
155
+
156
+				if ($end < $start) throw new Exception\RequestedRangeNotSatisfiable('The end offset (' . $range[1] . ') is lower than the start offset (' . $range[0] . ')');
157
+				if ($end >= $nodeSize) $end = $nodeSize - 1;
158
+
159
+			} else {
160
+
161
+				$start = $nodeSize - $range[1];
162
+				$end  = $nodeSize - 1;
163
+
164
+				if ($start < 0) $start = 0;
165
+
166
+			}
167
+
168
+			// Streams may advertise themselves as seekable, but still not
169
+			// actually allow fseek.  We'll manually go forward in the stream
170
+			// if fseek failed.
171
+			if (!stream_get_meta_data($body)['seekable'] || fseek($body, $start, SEEK_SET) === -1) {
172
+				$consumeBlock = 8192;
173
+				for ($consumed = 0; $start - $consumed > 0;){
174
+					if (feof($body)) throw new Exception\RequestedRangeNotSatisfiable('The start offset (' . $start . ') exceeded the size of the entity (' . $consumed . ')');
175
+					$consumed += strlen(fread($body, min($start - $consumed, $consumeBlock)));
176
+				}
177
+			}
178
+
179
+			$response->setHeader('Content-Length', $end - $start + 1);
180
+			$response->setHeader('Content-Range', 'bytes ' . $start . '-' . $end . '/' . $nodeSize);
181
+			$response->setStatus(206);
182
+			$response->setBody($body);
183
+
184
+		} else {
185
+
186
+			if ($nodeSize) $response->setHeader('Content-Length', $nodeSize);
187
+			$response->setStatus(200);
188
+			$response->setBody($body);
189
+
190
+		}
191
+		// Sending back false will interupt the event chain and tell the server
192
+		// we've handled this method.
193
+		return false;
194
+
195
+	}
196
+
197
+	/**
198
+	 * HTTP OPTIONS
199
+	 *
200
+	 * @param RequestInterface $request
201
+	 * @param ResponseInterface $response
202
+	 * @return bool
203
+	 */
204
+	public function httpOptions(RequestInterface $request, ResponseInterface $response) {
205
+
206
+		$methods = $this->server->getAllowedMethods($request->getPath());
207
+
208
+		$response->setHeader('Allow', strtoupper(implode(', ', $methods)));
209
+		$features = ['1', '3', 'extended-mkcol'];
210
+
211
+		foreach ($this->server->getPlugins() as $plugin) {
212
+			$features = array_merge($features, $plugin->getFeatures());
213
+		}
214
+
215
+		$response->setHeader('DAV', implode(', ', $features));
216
+		$response->setHeader('MS-Author-Via', 'DAV');
217
+		$response->setHeader('Accept-Ranges', 'bytes');
218
+		$response->setHeader('Content-Length', '0');
219
+		$response->setStatus(200);
220
+
221
+		// Sending back false will interupt the event chain and tell the server
222
+		// we've handled this method.
223
+		return false;
224
+
225
+	}
226
+
227
+	/**
228
+	 * HTTP HEAD
229
+	 *
230
+	 * This method is normally used to take a peak at a url, and only get the
231
+	 * HTTP response headers, without the body. This is used by clients to
232
+	 * determine if a remote file was changed, so they can use a local cached
233
+	 * version, instead of downloading it again
234
+	 *
235
+	 * @param RequestInterface $request
236
+	 * @param ResponseInterface $response
237
+	 * @return bool
238
+	 */
239
+	public function httpHead(RequestInterface $request, ResponseInterface $response) {
240
+
241
+		// This is implemented by changing the HEAD request to a GET request,
242
+		// and dropping the response body.
243
+		$subRequest = clone $request;
244
+		$subRequest->setMethod('GET');
245
+
246
+		try {
247
+			$this->server->invokeMethod($subRequest, $response, false);
248
+			$response->setBody('');
249
+		} catch (Exception\NotImplemented $e) {
250
+			// Some clients may do HEAD requests on collections, however, GET
251
+			// requests and HEAD requests _may_ not be defined on a collection,
252
+			// which would trigger a 501.
253
+			// This breaks some clients though, so we're transforming these
254
+			// 501s into 200s.
255
+			$response->setStatus(200);
256
+			$response->setBody('');
257
+			$response->setHeader('Content-Type', 'text/plain');
258
+			$response->setHeader('X-Sabre-Real-Status', $e->getHTTPCode());
259
+		}
260
+
261
+		// Sending back false will interupt the event chain and tell the server
262
+		// we've handled this method.
263
+		return false;
264
+
265
+	}
266
+
267
+	/**
268
+	 * HTTP Delete
269
+	 *
270
+	 * The HTTP delete method, deletes a given uri
271
+	 *
272
+	 * @param RequestInterface $request
273
+	 * @param ResponseInterface $response
274
+	 * @return void
275
+	 */
276
+	public function httpDelete(RequestInterface $request, ResponseInterface $response) {
277
+
278
+		$path = $request->getPath();
279
+
280
+		if (!$this->server->emit('beforeUnbind', [$path])) return false;
281
+		$this->server->tree->delete($path);
282
+		$this->server->emit('afterUnbind', [$path]);
283
+
284
+		$response->setStatus(204);
285
+		$response->setHeader('Content-Length', '0');
286
+
287
+		// Sending back false will interupt the event chain and tell the server
288
+		// we've handled this method.
289
+		return false;
290
+
291
+	}
292
+
293
+	/**
294
+	 * WebDAV PROPFIND
295
+	 *
296
+	 * This WebDAV method requests information about an uri resource, or a list of resources
297
+	 * If a client wants to receive the properties for a single resource it will add an HTTP Depth: header with a 0 value
298
+	 * If the value is 1, it means that it also expects a list of sub-resources (e.g.: files in a directory)
299
+	 *
300
+	 * The request body contains an XML data structure that has a list of properties the client understands
301
+	 * The response body is also an xml document, containing information about every uri resource and the requested properties
302
+	 *
303
+	 * It has to return a HTTP 207 Multi-status status code
304
+	 *
305
+	 * @param RequestInterface $request
306
+	 * @param ResponseInterface $response
307
+	 * @return void
308
+	 */
309
+	public function httpPropFind(RequestInterface $request, ResponseInterface $response) {
310
+
311
+		$path = $request->getPath();
312
+
313
+		$requestBody = $request->getBodyAsString();
314
+		if (strlen($requestBody)) {
315
+			try {
316
+				$propFindXml = $this->server->xml->expect('{DAV:}propfind', $requestBody);
317
+			} catch (ParseException $e) {
318
+				throw new BadRequest($e->getMessage(), null, $e);
319
+			}
320
+		} else {
321
+			$propFindXml = new Xml\Request\PropFind();
322
+			$propFindXml->allProp = true;
323
+			$propFindXml->properties = [];
324
+		}
325
+
326
+		$depth = $this->server->getHTTPDepth(1);
327
+		// The only two options for the depth of a propfind is 0 or 1 - as long as depth infinity is not enabled
328
+		if (!$this->server->enablePropfindDepthInfinity && $depth != 0) $depth = 1;
329
+
330
+		$newProperties = $this->server->getPropertiesForPath($path, $propFindXml->properties, $depth);
331
+
332
+		// This is a multi-status response
333
+		$response->setStatus(207);
334
+		$response->setHeader('Content-Type', 'application/xml; charset=utf-8');
335
+		$response->setHeader('Vary', 'Brief,Prefer');
336
+
337
+		// Normally this header is only needed for OPTIONS responses, however..
338
+		// iCal seems to also depend on these being set for PROPFIND. Since
339
+		// this is not harmful, we'll add it.
340
+		$features = ['1', '3', 'extended-mkcol'];
341
+		foreach ($this->server->getPlugins() as $plugin) {
342
+			$features = array_merge($features, $plugin->getFeatures());
343
+		}
344
+		$response->setHeader('DAV', implode(', ', $features));
345
+
346
+		$prefer = $this->server->getHTTPPrefer();
347
+		$minimal = $prefer['return'] === 'minimal';
348
+
349
+		$data = $this->server->generateMultiStatus($newProperties, $minimal);
350
+		$response->setBody($data);
351
+
352
+		// Sending back false will interupt the event chain and tell the server
353
+		// we've handled this method.
354
+		return false;
355
+
356
+	}
357
+
358
+	/**
359
+	 * WebDAV PROPPATCH
360
+	 *
361
+	 * This method is called to update properties on a Node. The request is an XML body with all the mutations.
362
+	 * In this XML body it is specified which properties should be set/updated and/or deleted
363
+	 *
364
+	 * @param RequestInterface $request
365
+	 * @param ResponseInterface $response
366
+	 * @return bool
367
+	 */
368
+	public function httpPropPatch(RequestInterface $request, ResponseInterface $response) {
369
+
370
+		$path = $request->getPath();
371
+
372
+		try {
373
+			$propPatch = $this->server->xml->expect('{DAV:}propertyupdate', $request->getBody());
374
+		} catch (ParseException $e) {
375
+			throw new BadRequest($e->getMessage(), null, $e);
376
+		}
377
+		$newProperties = $propPatch->properties;
378
+
379
+		$result = $this->server->updateProperties($path, $newProperties);
380
+
381
+		$prefer = $this->server->getHTTPPrefer();
382
+		$response->setHeader('Vary', 'Brief,Prefer');
383
+
384
+		if ($prefer['return'] === 'minimal') {
385
+
386
+			// If return-minimal is specified, we only have to check if the
387
+			// request was succesful, and don't need to return the
388
+			// multi-status.
389
+			$ok = true;
390
+			foreach ($result as $prop => $code) {
391
+				if ((int)$code > 299) {
392
+					$ok = false;
393
+				}
394
+			}
395
+
396
+			if ($ok) {
397
+
398
+				$response->setStatus(204);
399
+				return false;
400
+
401
+			}
402
+
403
+		}
404
+
405
+		$response->setStatus(207);
406
+		$response->setHeader('Content-Type', 'application/xml; charset=utf-8');
407
+
408
+
409
+		// Reorganizing the result for generateMultiStatus
410
+		$multiStatus = [];
411
+		foreach ($result as $propertyName => $code) {
412
+			if (isset($multiStatus[$code])) {
413
+				$multiStatus[$code][$propertyName] = null;
414
+			} else {
415
+				$multiStatus[$code] = [$propertyName => null];
416
+			}
417
+		}
418
+		$multiStatus['href'] = $path;
419
+
420
+		$response->setBody(
421
+			$this->server->generateMultiStatus([$multiStatus])
422
+		);
423
+
424
+		// Sending back false will interupt the event chain and tell the server
425
+		// we've handled this method.
426
+		return false;
427
+
428
+	}
429
+
430
+	/**
431
+	 * HTTP PUT method
432
+	 *
433
+	 * This HTTP method updates a file, or creates a new one.
434
+	 *
435
+	 * If a new resource was created, a 201 Created status code should be returned. If an existing resource is updated, it's a 204 No Content
436
+	 *
437
+	 * @param RequestInterface $request
438
+	 * @param ResponseInterface $response
439
+	 * @return bool
440
+	 */
441
+	public function httpPut(RequestInterface $request, ResponseInterface $response) {
442
+
443
+		$body = $request->getBodyAsStream();
444
+		$path = $request->getPath();
445
+
446
+		// Intercepting Content-Range
447
+		if ($request->getHeader('Content-Range')) {
448
+			/*
449 449
                An origin server that allows PUT on a given target resource MUST send
450 450
                a 400 (Bad Request) response to a PUT request that contains a
451 451
                Content-Range header field.
452 452
 
453 453
                Reference: http://tools.ietf.org/html/rfc7231#section-4.3.4
454 454
             */
455
-            throw new Exception\BadRequest('Content-Range on PUT requests are forbidden.');
456
-        }
455
+			throw new Exception\BadRequest('Content-Range on PUT requests are forbidden.');
456
+		}
457 457
 
458
-        // Intercepting the Finder problem
459
-        if (($expected = $request->getHeader('X-Expected-Entity-Length')) && $expected > 0) {
458
+		// Intercepting the Finder problem
459
+		if (($expected = $request->getHeader('X-Expected-Entity-Length')) && $expected > 0) {
460 460
 
461
-            /*
461
+			/*
462 462
             Many webservers will not cooperate well with Finder PUT requests,
463 463
             because it uses 'Chunked' transfer encoding for the request body.
464 464
 
@@ -479,445 +479,445 @@  discard block
 block discarded – undo
479 479
             protect the end-user.
480 480
             */
481 481
 
482
-            // Only reading first byte
483
-            $firstByte = fread($body, 1);
484
-            if (strlen($firstByte) !== 1) {
485
-                throw new Exception\Forbidden('This server is not compatible with OS/X finder. Consider using a different WebDAV client or webserver.');
486
-            }
482
+			// Only reading first byte
483
+			$firstByte = fread($body, 1);
484
+			if (strlen($firstByte) !== 1) {
485
+				throw new Exception\Forbidden('This server is not compatible with OS/X finder. Consider using a different WebDAV client or webserver.');
486
+			}
487 487
 
488
-            // The body needs to stay intact, so we copy everything to a
489
-            // temporary stream.
488
+			// The body needs to stay intact, so we copy everything to a
489
+			// temporary stream.
490 490
 
491
-            $newBody = fopen('php://temp', 'r+');
492
-            fwrite($newBody, $firstByte);
493
-            stream_copy_to_stream($body, $newBody);
494
-            rewind($newBody);
491
+			$newBody = fopen('php://temp', 'r+');
492
+			fwrite($newBody, $firstByte);
493
+			stream_copy_to_stream($body, $newBody);
494
+			rewind($newBody);
495 495
 
496
-            $body = $newBody;
496
+			$body = $newBody;
497 497
 
498
-        }
498
+		}
499 499
 
500
-        if ($this->server->tree->nodeExists($path)) {
500
+		if ($this->server->tree->nodeExists($path)) {
501 501
 
502
-            $node = $this->server->tree->getNodeForPath($path);
502
+			$node = $this->server->tree->getNodeForPath($path);
503 503
 
504
-            // If the node is a collection, we'll deny it
505
-            if (!($node instanceof IFile)) throw new Exception\Conflict('PUT is not allowed on non-files.');
504
+			// If the node is a collection, we'll deny it
505
+			if (!($node instanceof IFile)) throw new Exception\Conflict('PUT is not allowed on non-files.');
506 506
 
507
-            if (!$this->server->updateFile($path, $body, $etag)) {
508
-                return false;
509
-            }
507
+			if (!$this->server->updateFile($path, $body, $etag)) {
508
+				return false;
509
+			}
510 510
 
511
-            $response->setHeader('Content-Length', '0');
512
-            if ($etag) $response->setHeader('ETag', $etag);
513
-            $response->setStatus(204);
511
+			$response->setHeader('Content-Length', '0');
512
+			if ($etag) $response->setHeader('ETag', $etag);
513
+			$response->setStatus(204);
514 514
 
515
-        } else {
515
+		} else {
516 516
 
517
-            $etag = null;
518
-            // If we got here, the resource didn't exist yet.
519
-            if (!$this->server->createFile($path, $body, $etag)) {
520
-                // For one reason or another the file was not created.
521
-                return false;
522
-            }
517
+			$etag = null;
518
+			// If we got here, the resource didn't exist yet.
519
+			if (!$this->server->createFile($path, $body, $etag)) {
520
+				// For one reason or another the file was not created.
521
+				return false;
522
+			}
523 523
 
524
-            $response->setHeader('Content-Length', '0');
525
-            if ($etag) $response->setHeader('ETag', $etag);
526
-            $response->setStatus(201);
524
+			$response->setHeader('Content-Length', '0');
525
+			if ($etag) $response->setHeader('ETag', $etag);
526
+			$response->setStatus(201);
527 527
 
528
-        }
528
+		}
529 529
 
530
-        // Sending back false will interupt the event chain and tell the server
531
-        // we've handled this method.
532
-        return false;
530
+		// Sending back false will interupt the event chain and tell the server
531
+		// we've handled this method.
532
+		return false;
533 533
 
534
-    }
534
+	}
535 535
 
536 536
 
537
-    /**
538
-     * WebDAV MKCOL
539
-     *
540
-     * The MKCOL method is used to create a new collection (directory) on the server
541
-     *
542
-     * @param RequestInterface $request
543
-     * @param ResponseInterface $response
544
-     * @return bool
545
-     */
546
-    public function httpMkcol(RequestInterface $request, ResponseInterface $response) {
537
+	/**
538
+	 * WebDAV MKCOL
539
+	 *
540
+	 * The MKCOL method is used to create a new collection (directory) on the server
541
+	 *
542
+	 * @param RequestInterface $request
543
+	 * @param ResponseInterface $response
544
+	 * @return bool
545
+	 */
546
+	public function httpMkcol(RequestInterface $request, ResponseInterface $response) {
547 547
 
548
-        $requestBody = $request->getBodyAsString();
549
-        $path = $request->getPath();
548
+		$requestBody = $request->getBodyAsString();
549
+		$path = $request->getPath();
550 550
 
551
-        if ($requestBody) {
551
+		if ($requestBody) {
552 552
 
553
-            $contentType = $request->getHeader('Content-Type');
554
-            if (strpos($contentType, 'application/xml') !== 0 && strpos($contentType, 'text/xml') !== 0) {
553
+			$contentType = $request->getHeader('Content-Type');
554
+			if (strpos($contentType, 'application/xml') !== 0 && strpos($contentType, 'text/xml') !== 0) {
555 555
 
556
-                // We must throw 415 for unsupported mkcol bodies
557
-                throw new Exception\UnsupportedMediaType('The request body for the MKCOL request must have an xml Content-Type');
556
+				// We must throw 415 for unsupported mkcol bodies
557
+				throw new Exception\UnsupportedMediaType('The request body for the MKCOL request must have an xml Content-Type');
558 558
 
559
-            }
559
+			}
560 560
 
561
-            try {
562
-                $mkcol = $this->server->xml->expect('{DAV:}mkcol', $requestBody);
563
-            } catch (\Sabre\Xml\ParseException $e) {
564
-                throw new Exception\BadRequest($e->getMessage(), null, $e);
565
-            }
561
+			try {
562
+				$mkcol = $this->server->xml->expect('{DAV:}mkcol', $requestBody);
563
+			} catch (\Sabre\Xml\ParseException $e) {
564
+				throw new Exception\BadRequest($e->getMessage(), null, $e);
565
+			}
566 566
 
567
-            $properties = $mkcol->getProperties();
567
+			$properties = $mkcol->getProperties();
568 568
 
569
-            if (!isset($properties['{DAV:}resourcetype']))
570
-                throw new Exception\BadRequest('The mkcol request must include a {DAV:}resourcetype property');
569
+			if (!isset($properties['{DAV:}resourcetype']))
570
+				throw new Exception\BadRequest('The mkcol request must include a {DAV:}resourcetype property');
571 571
 
572
-            $resourceType = $properties['{DAV:}resourcetype']->getValue();
573
-            unset($properties['{DAV:}resourcetype']);
572
+			$resourceType = $properties['{DAV:}resourcetype']->getValue();
573
+			unset($properties['{DAV:}resourcetype']);
574 574
 
575
-        } else {
575
+		} else {
576 576
 
577
-            $properties = [];
578
-            $resourceType = ['{DAV:}collection'];
577
+			$properties = [];
578
+			$resourceType = ['{DAV:}collection'];
579 579
 
580
-        }
580
+		}
581 581
 
582
-        $mkcol = new MkCol($resourceType, $properties);
582
+		$mkcol = new MkCol($resourceType, $properties);
583 583
 
584
-        $result = $this->server->createCollection($path, $mkcol);
584
+		$result = $this->server->createCollection($path, $mkcol);
585 585
 
586
-        if (is_array($result)) {
587
-            $response->setStatus(207);
588
-            $response->setHeader('Content-Type', 'application/xml; charset=utf-8');
586
+		if (is_array($result)) {
587
+			$response->setStatus(207);
588
+			$response->setHeader('Content-Type', 'application/xml; charset=utf-8');
589 589
 
590
-            $response->setBody(
591
-                $this->server->generateMultiStatus([$result])
592
-            );
590
+			$response->setBody(
591
+				$this->server->generateMultiStatus([$result])
592
+			);
593 593
 
594
-        } else {
595
-            $response->setHeader('Content-Length', '0');
596
-            $response->setStatus(201);
597
-        }
594
+		} else {
595
+			$response->setHeader('Content-Length', '0');
596
+			$response->setStatus(201);
597
+		}
598 598
 
599
-        // Sending back false will interupt the event chain and tell the server
600
-        // we've handled this method.
601
-        return false;
599
+		// Sending back false will interupt the event chain and tell the server
600
+		// we've handled this method.
601
+		return false;
602 602
 
603
-    }
603
+	}
604 604
 
605
-    /**
606
-     * WebDAV HTTP MOVE method
607
-     *
608
-     * This method moves one uri to a different uri. A lot of the actual request processing is done in getCopyMoveInfo
609
-     *
610
-     * @param RequestInterface $request
611
-     * @param ResponseInterface $response
612
-     * @return bool
613
-     */
614
-    public function httpMove(RequestInterface $request, ResponseInterface $response) {
605
+	/**
606
+	 * WebDAV HTTP MOVE method
607
+	 *
608
+	 * This method moves one uri to a different uri. A lot of the actual request processing is done in getCopyMoveInfo
609
+	 *
610
+	 * @param RequestInterface $request
611
+	 * @param ResponseInterface $response
612
+	 * @return bool
613
+	 */
614
+	public function httpMove(RequestInterface $request, ResponseInterface $response) {
615 615
 
616
-        $path = $request->getPath();
616
+		$path = $request->getPath();
617 617
 
618
-        $moveInfo = $this->server->getCopyAndMoveInfo($request);
618
+		$moveInfo = $this->server->getCopyAndMoveInfo($request);
619 619
 
620
-        if ($moveInfo['destinationExists']) {
620
+		if ($moveInfo['destinationExists']) {
621 621
 
622
-            if (!$this->server->emit('beforeUnbind', [$moveInfo['destination']])) return false;
622
+			if (!$this->server->emit('beforeUnbind', [$moveInfo['destination']])) return false;
623 623
 
624
-        }
625
-        if (!$this->server->emit('beforeUnbind', [$path])) return false;
626
-        if (!$this->server->emit('beforeBind', [$moveInfo['destination']])) return false;
627
-        if (!$this->server->emit('beforeMove', [$path, $moveInfo['destination']])) return false;
624
+		}
625
+		if (!$this->server->emit('beforeUnbind', [$path])) return false;
626
+		if (!$this->server->emit('beforeBind', [$moveInfo['destination']])) return false;
627
+		if (!$this->server->emit('beforeMove', [$path, $moveInfo['destination']])) return false;
628 628
 
629
-        if ($moveInfo['destinationExists']) {
629
+		if ($moveInfo['destinationExists']) {
630 630
 
631
-            $this->server->tree->delete($moveInfo['destination']);
632
-            $this->server->emit('afterUnbind', [$moveInfo['destination']]);
631
+			$this->server->tree->delete($moveInfo['destination']);
632
+			$this->server->emit('afterUnbind', [$moveInfo['destination']]);
633 633
 
634
-        }
634
+		}
635 635
 
636
-        $this->server->tree->move($path, $moveInfo['destination']);
636
+		$this->server->tree->move($path, $moveInfo['destination']);
637 637
 
638
-        // Its important afterMove is called before afterUnbind, because it
639
-        // allows systems to transfer data from one path to another.
640
-        // PropertyStorage uses this. If afterUnbind was first, it would clean
641
-        // up all the properties before it has a chance.
642
-        $this->server->emit('afterMove', [$path, $moveInfo['destination']]);
643
-        $this->server->emit('afterUnbind', [$path]);
644
-        $this->server->emit('afterBind', [$moveInfo['destination']]);
638
+		// Its important afterMove is called before afterUnbind, because it
639
+		// allows systems to transfer data from one path to another.
640
+		// PropertyStorage uses this. If afterUnbind was first, it would clean
641
+		// up all the properties before it has a chance.
642
+		$this->server->emit('afterMove', [$path, $moveInfo['destination']]);
643
+		$this->server->emit('afterUnbind', [$path]);
644
+		$this->server->emit('afterBind', [$moveInfo['destination']]);
645 645
 
646
-        // If a resource was overwritten we should send a 204, otherwise a 201
647
-        $response->setHeader('Content-Length', '0');
648
-        $response->setStatus($moveInfo['destinationExists'] ? 204 : 201);
646
+		// If a resource was overwritten we should send a 204, otherwise a 201
647
+		$response->setHeader('Content-Length', '0');
648
+		$response->setStatus($moveInfo['destinationExists'] ? 204 : 201);
649 649
 
650
-        // Sending back false will interupt the event chain and tell the server
651
-        // we've handled this method.
652
-        return false;
650
+		// Sending back false will interupt the event chain and tell the server
651
+		// we've handled this method.
652
+		return false;
653 653
 
654
-    }
654
+	}
655 655
 
656
-    /**
657
-     * WebDAV HTTP COPY method
658
-     *
659
-     * This method copies one uri to a different uri, and works much like the MOVE request
660
-     * A lot of the actual request processing is done in getCopyMoveInfo
661
-     *
662
-     * @param RequestInterface $request
663
-     * @param ResponseInterface $response
664
-     * @return bool
665
-     */
666
-    public function httpCopy(RequestInterface $request, ResponseInterface $response) {
656
+	/**
657
+	 * WebDAV HTTP COPY method
658
+	 *
659
+	 * This method copies one uri to a different uri, and works much like the MOVE request
660
+	 * A lot of the actual request processing is done in getCopyMoveInfo
661
+	 *
662
+	 * @param RequestInterface $request
663
+	 * @param ResponseInterface $response
664
+	 * @return bool
665
+	 */
666
+	public function httpCopy(RequestInterface $request, ResponseInterface $response) {
667 667
 
668
-        $path = $request->getPath();
668
+		$path = $request->getPath();
669 669
 
670
-        $copyInfo = $this->server->getCopyAndMoveInfo($request);
670
+		$copyInfo = $this->server->getCopyAndMoveInfo($request);
671 671
 
672
-        if (!$this->server->emit('beforeBind', [$copyInfo['destination']])) return false;
673
-        if ($copyInfo['destinationExists']) {
674
-            if (!$this->server->emit('beforeUnbind', [$copyInfo['destination']])) return false;
675
-            $this->server->tree->delete($copyInfo['destination']);
676
-        }
677
-
678
-        $this->server->tree->copy($path, $copyInfo['destination']);
679
-        $this->server->emit('afterBind', [$copyInfo['destination']]);
680
-
681
-        // If a resource was overwritten we should send a 204, otherwise a 201
682
-        $response->setHeader('Content-Length', '0');
683
-        $response->setStatus($copyInfo['destinationExists'] ? 204 : 201);
684
-
685
-        // Sending back false will interupt the event chain and tell the server
686
-        // we've handled this method.
687
-        return false;
688
-
689
-
690
-    }
691
-
692
-    /**
693
-     * HTTP REPORT method implementation
694
-     *
695
-     * Although the REPORT method is not part of the standard WebDAV spec (it's from rfc3253)
696
-     * It's used in a lot of extensions, so it made sense to implement it into the core.
697
-     *
698
-     * @param RequestInterface $request
699
-     * @param ResponseInterface $response
700
-     * @return bool
701
-     */
702
-    public function httpReport(RequestInterface $request, ResponseInterface $response) {
703
-
704
-        $path = $request->getPath();
705
-
706
-        $result = $this->server->xml->parse(
707
-            $request->getBody(),
708
-            $request->getUrl(),
709
-            $rootElementName
710
-        );
711
-
712
-        if ($this->server->emit('report', [$rootElementName, $result, $path])) {
713
-
714
-            // If emit returned true, it means the report was not supported
715
-            throw new Exception\ReportNotSupported();
716
-
717
-        }
718
-
719
-        // Sending back false will interupt the event chain and tell the server
720
-        // we've handled this method.
721
-        return false;
722
-
723
-    }
724
-
725
-    /**
726
-     * This method is called during property updates.
727
-     *
728
-     * Here we check if a user attempted to update a protected property and
729
-     * ensure that the process fails if this is the case.
730
-     *
731
-     * @param string $path
732
-     * @param PropPatch $propPatch
733
-     * @return void
734
-     */
735
-    public function propPatchProtectedPropertyCheck($path, PropPatch $propPatch) {
736
-
737
-        // Comparing the mutation list to the list of propetected properties.
738
-        $mutations = $propPatch->getMutations();
739
-
740
-        $protected = array_intersect(
741
-            $this->server->protectedProperties,
742
-            array_keys($mutations)
743
-        );
744
-
745
-        if ($protected) {
746
-            $propPatch->setResultCode($protected, 403);
747
-        }
748
-
749
-    }
750
-
751
-    /**
752
-     * This method is called during property updates.
753
-     *
754
-     * Here we check if a node implements IProperties and let the node handle
755
-     * updating of (some) properties.
756
-     *
757
-     * @param string $path
758
-     * @param PropPatch $propPatch
759
-     * @return void
760
-     */
761
-    public function propPatchNodeUpdate($path, PropPatch $propPatch) {
762
-
763
-        // This should trigger a 404 if the node doesn't exist.
764
-        $node = $this->server->tree->getNodeForPath($path);
765
-
766
-        if ($node instanceof IProperties) {
767
-            $node->propPatch($propPatch);
768
-        }
769
-
770
-    }
771
-
772
-    /**
773
-     * This method is called when properties are retrieved.
774
-     *
775
-     * Here we add all the default properties.
776
-     *
777
-     * @param PropFind $propFind
778
-     * @param INode $node
779
-     * @return void
780
-     */
781
-    public function propFind(PropFind $propFind, INode $node) {
782
-
783
-        $propFind->handle('{DAV:}getlastmodified', function() use ($node) {
784
-            $lm = $node->getLastModified();
785
-            if ($lm) {
786
-                return new Xml\Property\GetLastModified($lm);
787
-            }
788
-        });
789
-
790
-        if ($node instanceof IFile) {
791
-            $propFind->handle('{DAV:}getcontentlength', [$node, 'getSize']);
792
-            $propFind->handle('{DAV:}getetag', [$node, 'getETag']);
793
-            $propFind->handle('{DAV:}getcontenttype', [$node, 'getContentType']);
794
-        }
795
-
796
-        if ($node instanceof IQuota) {
797
-            $quotaInfo = null;
798
-            $propFind->handle('{DAV:}quota-used-bytes', function() use (&$quotaInfo, $node) {
799
-                $quotaInfo = $node->getQuotaInfo();
800
-                return $quotaInfo[0];
801
-            });
802
-            $propFind->handle('{DAV:}quota-available-bytes', function() use (&$quotaInfo, $node) {
803
-                if (!$quotaInfo) {
804
-                    $quotaInfo = $node->getQuotaInfo();
805
-                }
806
-                return $quotaInfo[1];
807
-            });
808
-        }
809
-
810
-        $propFind->handle('{DAV:}supported-report-set', function() use ($propFind) {
811
-            $reports = [];
812
-            foreach ($this->server->getPlugins() as $plugin) {
813
-                $reports = array_merge($reports, $plugin->getSupportedReportSet($propFind->getPath()));
814
-            }
815
-            return new Xml\Property\SupportedReportSet($reports);
816
-        });
817
-        $propFind->handle('{DAV:}resourcetype', function() use ($node) {
818
-            return new Xml\Property\ResourceType($this->server->getResourceTypeForNode($node));
819
-        });
820
-        $propFind->handle('{DAV:}supported-method-set', function() use ($propFind) {
821
-            return new Xml\Property\SupportedMethodSet(
822
-                $this->server->getAllowedMethods($propFind->getPath())
823
-            );
824
-        });
825
-
826
-    }
827
-
828
-    /**
829
-     * Fetches properties for a node.
830
-     *
831
-     * This event is called a bit later, so plugins have a chance first to
832
-     * populate the result.
833
-     *
834
-     * @param PropFind $propFind
835
-     * @param INode $node
836
-     * @return void
837
-     */
838
-    public function propFindNode(PropFind $propFind, INode $node) {
839
-
840
-        if ($node instanceof IProperties && $propertyNames = $propFind->get404Properties()) {
841
-
842
-            $nodeProperties = $node->getProperties($propertyNames);
843
-            foreach ($propertyNames as $propertyName) {
844
-                if (array_key_exists($propertyName, $nodeProperties)) {
845
-                    $propFind->set($propertyName, $nodeProperties[$propertyName], 200);
846
-                }
847
-            }
848
-
849
-        }
850
-
851
-    }
852
-
853
-    /**
854
-     * This method is called when properties are retrieved.
855
-     *
856
-     * This specific handler is called very late in the process, because we
857
-     * want other systems to first have a chance to handle the properties.
858
-     *
859
-     * @param PropFind $propFind
860
-     * @param INode $node
861
-     * @return void
862
-     */
863
-    public function propFindLate(PropFind $propFind, INode $node) {
864
-
865
-        $propFind->handle('{http://calendarserver.org/ns/}getctag', function() use ($propFind) {
866
-
867
-            // If we already have a sync-token from the current propFind
868
-            // request, we can re-use that.
869
-            $val = $propFind->get('{http://sabredav.org/ns}sync-token');
870
-            if ($val) return $val;
871
-
872
-            $val = $propFind->get('{DAV:}sync-token');
873
-            if ($val && is_scalar($val)) {
874
-                return $val;
875
-            }
876
-            if ($val && $val instanceof Xml\Property\Href) {
877
-                return substr($val->getHref(), strlen(Sync\Plugin::SYNCTOKEN_PREFIX));
878
-            }
879
-
880
-            // If we got here, the earlier two properties may simply not have
881
-            // been part of the earlier request. We're going to fetch them.
882
-            $result = $this->server->getProperties($propFind->getPath(), [
883
-                '{http://sabredav.org/ns}sync-token',
884
-                '{DAV:}sync-token',
885
-            ]);
886
-
887
-            if (isset($result['{http://sabredav.org/ns}sync-token'])) {
888
-                return $result['{http://sabredav.org/ns}sync-token'];
889
-            }
890
-            if (isset($result['{DAV:}sync-token'])) {
891
-                $val = $result['{DAV:}sync-token'];
892
-                if (is_scalar($val)) {
893
-                    return $val;
894
-                } elseif ($val instanceof Xml\Property\Href) {
895
-                    return substr($val->getHref(), strlen(Sync\Plugin::SYNCTOKEN_PREFIX));
896
-                }
897
-            }
898
-
899
-        });
900
-
901
-    }
902
-
903
-    /**
904
-     * Returns a bunch of meta-data about the plugin.
905
-     *
906
-     * Providing this information is optional, and is mainly displayed by the
907
-     * Browser plugin.
908
-     *
909
-     * The description key in the returned array may contain html and will not
910
-     * be sanitized.
911
-     *
912
-     * @return array
913
-     */
914
-    public function getPluginInfo() {
915
-
916
-        return [
917
-            'name'        => $this->getPluginName(),
918
-            'description' => 'The Core plugin provides a lot of the basic functionality required by WebDAV, such as a default implementation for all HTTP and WebDAV methods.',
919
-            'link'        => null,
920
-        ];
921
-
922
-    }
672
+		if (!$this->server->emit('beforeBind', [$copyInfo['destination']])) return false;
673
+		if ($copyInfo['destinationExists']) {
674
+			if (!$this->server->emit('beforeUnbind', [$copyInfo['destination']])) return false;
675
+			$this->server->tree->delete($copyInfo['destination']);
676
+		}
677
+
678
+		$this->server->tree->copy($path, $copyInfo['destination']);
679
+		$this->server->emit('afterBind', [$copyInfo['destination']]);
680
+
681
+		// If a resource was overwritten we should send a 204, otherwise a 201
682
+		$response->setHeader('Content-Length', '0');
683
+		$response->setStatus($copyInfo['destinationExists'] ? 204 : 201);
684
+
685
+		// Sending back false will interupt the event chain and tell the server
686
+		// we've handled this method.
687
+		return false;
688
+
689
+
690
+	}
691
+
692
+	/**
693
+	 * HTTP REPORT method implementation
694
+	 *
695
+	 * Although the REPORT method is not part of the standard WebDAV spec (it's from rfc3253)
696
+	 * It's used in a lot of extensions, so it made sense to implement it into the core.
697
+	 *
698
+	 * @param RequestInterface $request
699
+	 * @param ResponseInterface $response
700
+	 * @return bool
701
+	 */
702
+	public function httpReport(RequestInterface $request, ResponseInterface $response) {
703
+
704
+		$path = $request->getPath();
705
+
706
+		$result = $this->server->xml->parse(
707
+			$request->getBody(),
708
+			$request->getUrl(),
709
+			$rootElementName
710
+		);
711
+
712
+		if ($this->server->emit('report', [$rootElementName, $result, $path])) {
713
+
714
+			// If emit returned true, it means the report was not supported
715
+			throw new Exception\ReportNotSupported();
716
+
717
+		}
718
+
719
+		// Sending back false will interupt the event chain and tell the server
720
+		// we've handled this method.
721
+		return false;
722
+
723
+	}
724
+
725
+	/**
726
+	 * This method is called during property updates.
727
+	 *
728
+	 * Here we check if a user attempted to update a protected property and
729
+	 * ensure that the process fails if this is the case.
730
+	 *
731
+	 * @param string $path
732
+	 * @param PropPatch $propPatch
733
+	 * @return void
734
+	 */
735
+	public function propPatchProtectedPropertyCheck($path, PropPatch $propPatch) {
736
+
737
+		// Comparing the mutation list to the list of propetected properties.
738
+		$mutations = $propPatch->getMutations();
739
+
740
+		$protected = array_intersect(
741
+			$this->server->protectedProperties,
742
+			array_keys($mutations)
743
+		);
744
+
745
+		if ($protected) {
746
+			$propPatch->setResultCode($protected, 403);
747
+		}
748
+
749
+	}
750
+
751
+	/**
752
+	 * This method is called during property updates.
753
+	 *
754
+	 * Here we check if a node implements IProperties and let the node handle
755
+	 * updating of (some) properties.
756
+	 *
757
+	 * @param string $path
758
+	 * @param PropPatch $propPatch
759
+	 * @return void
760
+	 */
761
+	public function propPatchNodeUpdate($path, PropPatch $propPatch) {
762
+
763
+		// This should trigger a 404 if the node doesn't exist.
764
+		$node = $this->server->tree->getNodeForPath($path);
765
+
766
+		if ($node instanceof IProperties) {
767
+			$node->propPatch($propPatch);
768
+		}
769
+
770
+	}
771
+
772
+	/**
773
+	 * This method is called when properties are retrieved.
774
+	 *
775
+	 * Here we add all the default properties.
776
+	 *
777
+	 * @param PropFind $propFind
778
+	 * @param INode $node
779
+	 * @return void
780
+	 */
781
+	public function propFind(PropFind $propFind, INode $node) {
782
+
783
+		$propFind->handle('{DAV:}getlastmodified', function() use ($node) {
784
+			$lm = $node->getLastModified();
785
+			if ($lm) {
786
+				return new Xml\Property\GetLastModified($lm);
787
+			}
788
+		});
789
+
790
+		if ($node instanceof IFile) {
791
+			$propFind->handle('{DAV:}getcontentlength', [$node, 'getSize']);
792
+			$propFind->handle('{DAV:}getetag', [$node, 'getETag']);
793
+			$propFind->handle('{DAV:}getcontenttype', [$node, 'getContentType']);
794
+		}
795
+
796
+		if ($node instanceof IQuota) {
797
+			$quotaInfo = null;
798
+			$propFind->handle('{DAV:}quota-used-bytes', function() use (&$quotaInfo, $node) {
799
+				$quotaInfo = $node->getQuotaInfo();
800
+				return $quotaInfo[0];
801
+			});
802
+			$propFind->handle('{DAV:}quota-available-bytes', function() use (&$quotaInfo, $node) {
803
+				if (!$quotaInfo) {
804
+					$quotaInfo = $node->getQuotaInfo();
805
+				}
806
+				return $quotaInfo[1];
807
+			});
808
+		}
809
+
810
+		$propFind->handle('{DAV:}supported-report-set', function() use ($propFind) {
811
+			$reports = [];
812
+			foreach ($this->server->getPlugins() as $plugin) {
813
+				$reports = array_merge($reports, $plugin->getSupportedReportSet($propFind->getPath()));
814
+			}
815
+			return new Xml\Property\SupportedReportSet($reports);
816
+		});
817
+		$propFind->handle('{DAV:}resourcetype', function() use ($node) {
818
+			return new Xml\Property\ResourceType($this->server->getResourceTypeForNode($node));
819
+		});
820
+		$propFind->handle('{DAV:}supported-method-set', function() use ($propFind) {
821
+			return new Xml\Property\SupportedMethodSet(
822
+				$this->server->getAllowedMethods($propFind->getPath())
823
+			);
824
+		});
825
+
826
+	}
827
+
828
+	/**
829
+	 * Fetches properties for a node.
830
+	 *
831
+	 * This event is called a bit later, so plugins have a chance first to
832
+	 * populate the result.
833
+	 *
834
+	 * @param PropFind $propFind
835
+	 * @param INode $node
836
+	 * @return void
837
+	 */
838
+	public function propFindNode(PropFind $propFind, INode $node) {
839
+
840
+		if ($node instanceof IProperties && $propertyNames = $propFind->get404Properties()) {
841
+
842
+			$nodeProperties = $node->getProperties($propertyNames);
843
+			foreach ($propertyNames as $propertyName) {
844
+				if (array_key_exists($propertyName, $nodeProperties)) {
845
+					$propFind->set($propertyName, $nodeProperties[$propertyName], 200);
846
+				}
847
+			}
848
+
849
+		}
850
+
851
+	}
852
+
853
+	/**
854
+	 * This method is called when properties are retrieved.
855
+	 *
856
+	 * This specific handler is called very late in the process, because we
857
+	 * want other systems to first have a chance to handle the properties.
858
+	 *
859
+	 * @param PropFind $propFind
860
+	 * @param INode $node
861
+	 * @return void
862
+	 */
863
+	public function propFindLate(PropFind $propFind, INode $node) {
864
+
865
+		$propFind->handle('{http://calendarserver.org/ns/}getctag', function() use ($propFind) {
866
+
867
+			// If we already have a sync-token from the current propFind
868
+			// request, we can re-use that.
869
+			$val = $propFind->get('{http://sabredav.org/ns}sync-token');
870
+			if ($val) return $val;
871
+
872
+			$val = $propFind->get('{DAV:}sync-token');
873
+			if ($val && is_scalar($val)) {
874
+				return $val;
875
+			}
876
+			if ($val && $val instanceof Xml\Property\Href) {
877
+				return substr($val->getHref(), strlen(Sync\Plugin::SYNCTOKEN_PREFIX));
878
+			}
879
+
880
+			// If we got here, the earlier two properties may simply not have
881
+			// been part of the earlier request. We're going to fetch them.
882
+			$result = $this->server->getProperties($propFind->getPath(), [
883
+				'{http://sabredav.org/ns}sync-token',
884
+				'{DAV:}sync-token',
885
+			]);
886
+
887
+			if (isset($result['{http://sabredav.org/ns}sync-token'])) {
888
+				return $result['{http://sabredav.org/ns}sync-token'];
889
+			}
890
+			if (isset($result['{DAV:}sync-token'])) {
891
+				$val = $result['{DAV:}sync-token'];
892
+				if (is_scalar($val)) {
893
+					return $val;
894
+				} elseif ($val instanceof Xml\Property\Href) {
895
+					return substr($val->getHref(), strlen(Sync\Plugin::SYNCTOKEN_PREFIX));
896
+				}
897
+			}
898
+
899
+		});
900
+
901
+	}
902
+
903
+	/**
904
+	 * Returns a bunch of meta-data about the plugin.
905
+	 *
906
+	 * Providing this information is optional, and is mainly displayed by the
907
+	 * Browser plugin.
908
+	 *
909
+	 * The description key in the returned array may contain html and will not
910
+	 * be sanitized.
911
+	 *
912
+	 * @return array
913
+	 */
914
+	public function getPluginInfo() {
915
+
916
+		return [
917
+			'name'        => $this->getPluginName(),
918
+			'description' => 'The Core plugin provides a lot of the basic functionality required by WebDAV, such as a default implementation for all HTTP and WebDAV methods.',
919
+			'link'        => null,
920
+		];
921
+
922
+	}
923 923
 }
Please login to merge, or discard this patch.
Doc Comments   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -71,7 +71,7 @@  discard block
 block discarded – undo
71 71
      *
72 72
      * @param RequestInterface $request
73 73
      * @param ResponseInterface $response
74
-     * @return bool
74
+     * @return null|false
75 75
      */
76 76
     public function httpGet(RequestInterface $request, ResponseInterface $response) {
77 77
 
@@ -271,7 +271,7 @@  discard block
 block discarded – undo
271 271
      *
272 272
      * @param RequestInterface $request
273 273
      * @param ResponseInterface $response
274
-     * @return void
274
+     * @return boolean
275 275
      */
276 276
     public function httpDelete(RequestInterface $request, ResponseInterface $response) {
277 277
 
@@ -304,7 +304,7 @@  discard block
 block discarded – undo
304 304
      *
305 305
      * @param RequestInterface $request
306 306
      * @param ResponseInterface $response
307
-     * @return void
307
+     * @return boolean
308 308
      */
309 309
     public function httpPropFind(RequestInterface $request, ResponseInterface $response) {
310 310
 
Please login to merge, or discard this patch.
Spacing   +19 added lines, -19 removed lines patch added patch discarded remove patch
@@ -32,23 +32,23 @@  discard block
 block discarded – undo
32 32
     public function initialize(Server $server) {
33 33
 
34 34
         $this->server = $server;
35
-        $server->on('method:GET',       [$this, 'httpGet']);
36
-        $server->on('method:OPTIONS',   [$this, 'httpOptions']);
37
-        $server->on('method:HEAD',      [$this, 'httpHead']);
38
-        $server->on('method:DELETE',    [$this, 'httpDelete']);
39
-        $server->on('method:PROPFIND',  [$this, 'httpPropFind']);
35
+        $server->on('method:GET', [$this, 'httpGet']);
36
+        $server->on('method:OPTIONS', [$this, 'httpOptions']);
37
+        $server->on('method:HEAD', [$this, 'httpHead']);
38
+        $server->on('method:DELETE', [$this, 'httpDelete']);
39
+        $server->on('method:PROPFIND', [$this, 'httpPropFind']);
40 40
         $server->on('method:PROPPATCH', [$this, 'httpPropPatch']);
41
-        $server->on('method:PUT',       [$this, 'httpPut']);
42
-        $server->on('method:MKCOL',     [$this, 'httpMkcol']);
43
-        $server->on('method:MOVE',      [$this, 'httpMove']);
44
-        $server->on('method:COPY',      [$this, 'httpCopy']);
45
-        $server->on('method:REPORT',    [$this, 'httpReport']);
46
-
47
-        $server->on('propPatch',        [$this, 'propPatchProtectedPropertyCheck'], 90);
48
-        $server->on('propPatch',        [$this, 'propPatchNodeUpdate'], 200);
49
-        $server->on('propFind',         [$this, 'propFind']);
50
-        $server->on('propFind',         [$this, 'propFindNode'], 120);
51
-        $server->on('propFind',         [$this, 'propFindLate'], 200);
41
+        $server->on('method:PUT', [$this, 'httpPut']);
42
+        $server->on('method:MKCOL', [$this, 'httpMkcol']);
43
+        $server->on('method:MOVE', [$this, 'httpMove']);
44
+        $server->on('method:COPY', [$this, 'httpCopy']);
45
+        $server->on('method:REPORT', [$this, 'httpReport']);
46
+
47
+        $server->on('propPatch', [$this, 'propPatchProtectedPropertyCheck'], 90);
48
+        $server->on('propPatch', [$this, 'propPatchNodeUpdate'], 200);
49
+        $server->on('propFind', [$this, 'propFind']);
50
+        $server->on('propFind', [$this, 'propFindNode'], 120);
51
+        $server->on('propFind', [$this, 'propFindLate'], 200);
52 52
 
53 53
     }
54 54
 
@@ -159,7 +159,7 @@  discard block
 block discarded – undo
159 159
             } else {
160 160
 
161 161
                 $start = $nodeSize - $range[1];
162
-                $end  = $nodeSize - 1;
162
+                $end = $nodeSize - 1;
163 163
 
164 164
                 if ($start < 0) $start = 0;
165 165
 
@@ -170,7 +170,7 @@  discard block
 block discarded – undo
170 170
             // if fseek failed.
171 171
             if (!stream_get_meta_data($body)['seekable'] || fseek($body, $start, SEEK_SET) === -1) {
172 172
                 $consumeBlock = 8192;
173
-                for ($consumed = 0; $start - $consumed > 0;){
173
+                for ($consumed = 0; $start - $consumed > 0;) {
174 174
                     if (feof($body)) throw new Exception\RequestedRangeNotSatisfiable('The start offset (' . $start . ') exceeded the size of the entity (' . $consumed . ')');
175 175
                     $consumed += strlen(fread($body, min($start - $consumed, $consumeBlock)));
176 176
                 }
@@ -388,7 +388,7 @@  discard block
 block discarded – undo
388 388
             // multi-status.
389 389
             $ok = true;
390 390
             foreach ($result as $prop => $code) {
391
-                if ((int)$code > 299) {
391
+                if ((int) $code > 299) {
392 392
                     $ok = false;
393 393
                 }
394 394
             }
Please login to merge, or discard this patch.
libraries/SabreDAV/DAV/UUIDUtil.php 1 patch
Indentation   +37 added lines, -37 removed lines patch added patch discarded remove patch
@@ -15,50 +15,50 @@
 block discarded – undo
15 15
  */
16 16
 class UUIDUtil {
17 17
 
18
-    /**
19
-     * Returns a pseudo-random v4 UUID
20
-     *
21
-     * This function is based on a comment by Andrew Moore on php.net
22
-     *
23
-     * @see http://www.php.net/manual/en/function.uniqid.php#94959
24
-     * @return string
25
-     */
26
-    static function getUUID() {
18
+	/**
19
+	 * Returns a pseudo-random v4 UUID
20
+	 *
21
+	 * This function is based on a comment by Andrew Moore on php.net
22
+	 *
23
+	 * @see http://www.php.net/manual/en/function.uniqid.php#94959
24
+	 * @return string
25
+	 */
26
+	static function getUUID() {
27 27
 
28
-        return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
29
-            // 32 bits for "time_low"
30
-            mt_rand(0, 0xffff), mt_rand(0, 0xffff),
28
+		return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
29
+			// 32 bits for "time_low"
30
+			mt_rand(0, 0xffff), mt_rand(0, 0xffff),
31 31
 
32
-            // 16 bits for "time_mid"
33
-            mt_rand(0, 0xffff),
32
+			// 16 bits for "time_mid"
33
+			mt_rand(0, 0xffff),
34 34
 
35
-            // 16 bits for "time_hi_and_version",
36
-            // four most significant bits holds version number 4
37
-            mt_rand(0, 0x0fff) | 0x4000,
35
+			// 16 bits for "time_hi_and_version",
36
+			// four most significant bits holds version number 4
37
+			mt_rand(0, 0x0fff) | 0x4000,
38 38
 
39
-            // 16 bits, 8 bits for "clk_seq_hi_res",
40
-            // 8 bits for "clk_seq_low",
41
-            // two most significant bits holds zero and one for variant DCE1.1
42
-            mt_rand(0, 0x3fff) | 0x8000,
39
+			// 16 bits, 8 bits for "clk_seq_hi_res",
40
+			// 8 bits for "clk_seq_low",
41
+			// two most significant bits holds zero and one for variant DCE1.1
42
+			mt_rand(0, 0x3fff) | 0x8000,
43 43
 
44
-            // 48 bits for "node"
45
-            mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
46
-        );
47
-    }
44
+			// 48 bits for "node"
45
+			mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
46
+		);
47
+	}
48 48
 
49
-    /**
50
-     * Checks if a string is a valid UUID.
51
-     *
52
-     * @param string $uuid
53
-     * @return bool
54
-     */
55
-    static function validateUUID($uuid) {
49
+	/**
50
+	 * Checks if a string is a valid UUID.
51
+	 *
52
+	 * @param string $uuid
53
+	 * @return bool
54
+	 */
55
+	static function validateUUID($uuid) {
56 56
 
57
-        return preg_match(
58
-            '/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i',
59
-            $uuid
60
-        ) !== 0;
57
+		return preg_match(
58
+			'/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i',
59
+			$uuid
60
+		) !== 0;
61 61
 
62
-    }
62
+	}
63 63
 
64 64
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/DAV/PropertyStorage/Backend/PDO.php 2 patches
Braces   +3 added lines, -1 removed lines patch added patch discarded remove patch
@@ -201,7 +201,9 @@
 block discarded – undo
201 201
 
202 202
             // Sanity check. SQL may select too many records, such as records
203 203
             // with different cases.
204
-            if ($row['path'] !== $source && strpos($row['path'], $source . '/') !== 0) continue;
204
+            if ($row['path'] !== $source && strpos($row['path'], $source . '/') !== 0) {
205
+            	continue;
206
+            }
205 207
 
206 208
             $trailingPart = substr($row['path'], strlen($source) + 1);
207 209
             $newPath = $destination;
Please login to merge, or discard this patch.
Indentation   +193 added lines, -193 removed lines patch added patch discarded remove patch
@@ -20,198 +20,198 @@
 block discarded – undo
20 20
  */
21 21
 class PDO implements BackendInterface {
22 22
 
23
-    /**
24
-     * Value is stored as string.
25
-     */
26
-    const VT_STRING = 1;
27
-
28
-    /**
29
-     * Value is stored as XML fragment.
30
-     */
31
-    const VT_XML = 2;
32
-
33
-    /**
34
-     * Value is stored as a property object.
35
-     */
36
-    const VT_OBJECT = 3;
37
-
38
-    /**
39
-     * PDO
40
-     *
41
-     * @var \PDO
42
-     */
43
-    protected $pdo;
44
-
45
-    /**
46
-     * PDO table name we'll be using
47
-     *
48
-     * @var string
49
-     */
50
-    public $tableName = 'propertystorage';
51
-
52
-    /**
53
-     * Creates the PDO property storage engine
54
-     *
55
-     * @param \PDO $pdo
56
-     */
57
-    public function __construct(\PDO $pdo) {
58
-
59
-        $this->pdo = $pdo;
60
-
61
-    }
62
-
63
-    /**
64
-     * Fetches properties for a path.
65
-     *
66
-     * This method received a PropFind object, which contains all the
67
-     * information about the properties that need to be fetched.
68
-     *
69
-     * Ususually you would just want to call 'get404Properties' on this object,
70
-     * as this will give you the _exact_ list of properties that need to be
71
-     * fetched, and haven't yet.
72
-     *
73
-     * However, you can also support the 'allprops' property here. In that
74
-     * case, you should check for $propFind->isAllProps().
75
-     *
76
-     * @param string $path
77
-     * @param PropFind $propFind
78
-     * @return void
79
-     */
80
-    public function propFind($path, PropFind $propFind) {
81
-
82
-        if (!$propFind->isAllProps() && count($propFind->get404Properties()) === 0) {
83
-            return;
84
-        }
85
-
86
-        $query = sprintf('SELECT name, value, valuetype FROM %s WHERE path = ?', $this->tableName);
87
-        $stmt = $this->pdo->prepare($query);
88
-        $stmt->execute([$path]);
89
-
90
-        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
91
-            switch ($row['valuetype']) {
92
-                case null :
93
-                case self::VT_STRING :
94
-                    $propFind->set($row['name'], $row['value']);
95
-                    break;
96
-                case self::VT_XML :
97
-                    $propFind->set($row['name'], new Complex($row['value']));
98
-                    break;
99
-                case self::VT_OBJECT :
100
-                    $propFind->set($row['name'], unserialize($row['value']));
101
-                    break;
102
-            }
103
-        }
104
-
105
-    }
106
-
107
-    /**
108
-     * Updates properties for a path
109
-     *
110
-     * This method received a PropPatch object, which contains all the
111
-     * information about the update.
112
-     *
113
-     * Usually you would want to call 'handleRemaining' on this object, to get;
114
-     * a list of all properties that need to be stored.
115
-     *
116
-     * @param string $path
117
-     * @param PropPatch $propPatch
118
-     * @return void
119
-     */
120
-    public function propPatch($path, PropPatch $propPatch) {
121
-
122
-        $propPatch->handleRemaining(function($properties) use ($path) {
123
-
124
-            $updateStmt = $this->pdo->prepare("REPLACE INTO " . $this->tableName . " (path, name, valuetype, value) VALUES (?, ?, ?, ?)");
125
-            $deleteStmt = $this->pdo->prepare(sprintf("DELETE FROM %s WHERE path = ? && name = ?", $this->tableName));
126
-
127
-            foreach ($properties as $name => $value) {
128
-
129
-                if (!is_null($value)) {
130
-                    if (is_scalar($value)) {
131
-                        $valueType = self::VT_STRING;
132
-                    } elseif ($value instanceof Complex) {
133
-                        $valueType = self::VT_XML;
134
-                        $value = $value->getXml();
135
-                    } else {
136
-                        $valueType = self::VT_OBJECT;
137
-                        $value = serialize($value);
138
-                    }
139
-                    $updateStmt->execute([$path, $name, $valueType, $value]);
140
-                } else {
141
-                    $deleteStmt->execute([$path, $name]);
142
-                }
143
-
144
-            }
145
-
146
-            return true;
147
-
148
-        });
149
-
150
-    }
151
-
152
-    /**
153
-     * This method is called after a node is deleted.
154
-     *
155
-     * This allows a backend to clean up all associated properties.
156
-     *
157
-     * The delete method will get called once for the deletion of an entire
158
-     * tree.
159
-     *
160
-     * @param string $path
161
-     * @return void
162
-     */
163
-    public function delete($path) {
164
-
165
-        $stmt = $this->pdo->prepare(sprintf("DELETE FROM %s  WHERE path = ? || path LIKE ? ESCAPE '='", $this->tableName));
166
-        $childPath = strtr(
167
-            $path,
168
-            [
169
-                '=' => '==',
170
-                '%' => '=%',
171
-                '_' => '=_'
172
-            ]
173
-        ) . '/%';
174
-
175
-        $stmt->execute([$path, $childPath]);
176
-
177
-    }
178
-
179
-    /**
180
-     * This method is called after a successful MOVE
181
-     *
182
-     * This should be used to migrate all properties from one path to another.
183
-     * Note that entire collections may be moved, so ensure that all properties
184
-     * for children are also moved along.
185
-     *
186
-     * @param string $source
187
-     * @param string $destination
188
-     * @return void
189
-     */
190
-    public function move($source, $destination) {
191
-
192
-        // I don't know a way to write this all in a single sql query that's
193
-        // also compatible across db engines, so we're letting PHP do all the
194
-        // updates. Much slower, but it should still be pretty fast in most
195
-        // cases.
196
-        $select = $this->pdo->prepare(sprintf('SELECT id, path FROM %s WHERE path = ? || path LIKE ?', $this->tableName));
197
-        $select->execute([$source, $source . '/%']);
198
-
199
-        $update = $this->pdo->prepare(sprintf('UPDATE %s SET path = ? WHERE id = ?', $this->tableName));
200
-        while ($row = $select->fetch(\PDO::FETCH_ASSOC)) {
201
-
202
-            // Sanity check. SQL may select too many records, such as records
203
-            // with different cases.
204
-            if ($row['path'] !== $source && strpos($row['path'], $source . '/') !== 0) continue;
205
-
206
-            $trailingPart = substr($row['path'], strlen($source) + 1);
207
-            $newPath = $destination;
208
-            if ($trailingPart) {
209
-                $newPath .= '/' . $trailingPart;
210
-            }
211
-            $update->execute([$newPath, $row['id']]);
212
-
213
-        }
214
-
215
-    }
23
+	/**
24
+	 * Value is stored as string.
25
+	 */
26
+	const VT_STRING = 1;
27
+
28
+	/**
29
+	 * Value is stored as XML fragment.
30
+	 */
31
+	const VT_XML = 2;
32
+
33
+	/**
34
+	 * Value is stored as a property object.
35
+	 */
36
+	const VT_OBJECT = 3;
37
+
38
+	/**
39
+	 * PDO
40
+	 *
41
+	 * @var \PDO
42
+	 */
43
+	protected $pdo;
44
+
45
+	/**
46
+	 * PDO table name we'll be using
47
+	 *
48
+	 * @var string
49
+	 */
50
+	public $tableName = 'propertystorage';
51
+
52
+	/**
53
+	 * Creates the PDO property storage engine
54
+	 *
55
+	 * @param \PDO $pdo
56
+	 */
57
+	public function __construct(\PDO $pdo) {
58
+
59
+		$this->pdo = $pdo;
60
+
61
+	}
62
+
63
+	/**
64
+	 * Fetches properties for a path.
65
+	 *
66
+	 * This method received a PropFind object, which contains all the
67
+	 * information about the properties that need to be fetched.
68
+	 *
69
+	 * Ususually you would just want to call 'get404Properties' on this object,
70
+	 * as this will give you the _exact_ list of properties that need to be
71
+	 * fetched, and haven't yet.
72
+	 *
73
+	 * However, you can also support the 'allprops' property here. In that
74
+	 * case, you should check for $propFind->isAllProps().
75
+	 *
76
+	 * @param string $path
77
+	 * @param PropFind $propFind
78
+	 * @return void
79
+	 */
80
+	public function propFind($path, PropFind $propFind) {
81
+
82
+		if (!$propFind->isAllProps() && count($propFind->get404Properties()) === 0) {
83
+			return;
84
+		}
85
+
86
+		$query = sprintf('SELECT name, value, valuetype FROM %s WHERE path = ?', $this->tableName);
87
+		$stmt = $this->pdo->prepare($query);
88
+		$stmt->execute([$path]);
89
+
90
+		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
91
+			switch ($row['valuetype']) {
92
+				case null :
93
+				case self::VT_STRING :
94
+					$propFind->set($row['name'], $row['value']);
95
+					break;
96
+				case self::VT_XML :
97
+					$propFind->set($row['name'], new Complex($row['value']));
98
+					break;
99
+				case self::VT_OBJECT :
100
+					$propFind->set($row['name'], unserialize($row['value']));
101
+					break;
102
+			}
103
+		}
104
+
105
+	}
106
+
107
+	/**
108
+	 * Updates properties for a path
109
+	 *
110
+	 * This method received a PropPatch object, which contains all the
111
+	 * information about the update.
112
+	 *
113
+	 * Usually you would want to call 'handleRemaining' on this object, to get;
114
+	 * a list of all properties that need to be stored.
115
+	 *
116
+	 * @param string $path
117
+	 * @param PropPatch $propPatch
118
+	 * @return void
119
+	 */
120
+	public function propPatch($path, PropPatch $propPatch) {
121
+
122
+		$propPatch->handleRemaining(function($properties) use ($path) {
123
+
124
+			$updateStmt = $this->pdo->prepare("REPLACE INTO " . $this->tableName . " (path, name, valuetype, value) VALUES (?, ?, ?, ?)");
125
+			$deleteStmt = $this->pdo->prepare(sprintf("DELETE FROM %s WHERE path = ? && name = ?", $this->tableName));
126
+
127
+			foreach ($properties as $name => $value) {
128
+
129
+				if (!is_null($value)) {
130
+					if (is_scalar($value)) {
131
+						$valueType = self::VT_STRING;
132
+					} elseif ($value instanceof Complex) {
133
+						$valueType = self::VT_XML;
134
+						$value = $value->getXml();
135
+					} else {
136
+						$valueType = self::VT_OBJECT;
137
+						$value = serialize($value);
138
+					}
139
+					$updateStmt->execute([$path, $name, $valueType, $value]);
140
+				} else {
141
+					$deleteStmt->execute([$path, $name]);
142
+				}
143
+
144
+			}
145
+
146
+			return true;
147
+
148
+		});
149
+
150
+	}
151
+
152
+	/**
153
+	 * This method is called after a node is deleted.
154
+	 *
155
+	 * This allows a backend to clean up all associated properties.
156
+	 *
157
+	 * The delete method will get called once for the deletion of an entire
158
+	 * tree.
159
+	 *
160
+	 * @param string $path
161
+	 * @return void
162
+	 */
163
+	public function delete($path) {
164
+
165
+		$stmt = $this->pdo->prepare(sprintf("DELETE FROM %s  WHERE path = ? || path LIKE ? ESCAPE '='", $this->tableName));
166
+		$childPath = strtr(
167
+			$path,
168
+			[
169
+				'=' => '==',
170
+				'%' => '=%',
171
+				'_' => '=_'
172
+			]
173
+		) . '/%';
174
+
175
+		$stmt->execute([$path, $childPath]);
176
+
177
+	}
178
+
179
+	/**
180
+	 * This method is called after a successful MOVE
181
+	 *
182
+	 * This should be used to migrate all properties from one path to another.
183
+	 * Note that entire collections may be moved, so ensure that all properties
184
+	 * for children are also moved along.
185
+	 *
186
+	 * @param string $source
187
+	 * @param string $destination
188
+	 * @return void
189
+	 */
190
+	public function move($source, $destination) {
191
+
192
+		// I don't know a way to write this all in a single sql query that's
193
+		// also compatible across db engines, so we're letting PHP do all the
194
+		// updates. Much slower, but it should still be pretty fast in most
195
+		// cases.
196
+		$select = $this->pdo->prepare(sprintf('SELECT id, path FROM %s WHERE path = ? || path LIKE ?', $this->tableName));
197
+		$select->execute([$source, $source . '/%']);
198
+
199
+		$update = $this->pdo->prepare(sprintf('UPDATE %s SET path = ? WHERE id = ?', $this->tableName));
200
+		while ($row = $select->fetch(\PDO::FETCH_ASSOC)) {
201
+
202
+			// Sanity check. SQL may select too many records, such as records
203
+			// with different cases.
204
+			if ($row['path'] !== $source && strpos($row['path'], $source . '/') !== 0) continue;
205
+
206
+			$trailingPart = substr($row['path'], strlen($source) + 1);
207
+			$newPath = $destination;
208
+			if ($trailingPart) {
209
+				$newPath .= '/' . $trailingPart;
210
+			}
211
+			$update->execute([$newPath, $row['id']]);
212
+
213
+		}
214
+
215
+	}
216 216
 
217 217
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/DAV/Collection.php 2 patches
Braces   +3 added lines, -1 removed lines patch added patch discarded remove patch
@@ -32,7 +32,9 @@
 block discarded – undo
32 32
 
33 33
         foreach ($this->getChildren() as $child) {
34 34
 
35
-            if ($child->getName() === $name) return $child;
35
+            if ($child->getName() === $name) {
36
+            	return $child;
37
+            }
36 38
 
37 39
         }
38 40
         throw new Exception\NotFound('File not found: ' . $name);
Please login to merge, or discard this patch.
Indentation   +90 added lines, -90 removed lines patch added patch discarded remove patch
@@ -14,96 +14,96 @@
 block discarded – undo
14 14
  */
15 15
 abstract class Collection extends Node implements ICollection {
16 16
 
17
-    /**
18
-     * Returns a child object, by its name.
19
-     *
20
-     * This method makes use of the getChildren method to grab all the child
21
-     * nodes, and compares the name.
22
-     * Generally its wise to override this, as this can usually be optimized
23
-     *
24
-     * This method must throw Sabre\DAV\Exception\NotFound if the node does not
25
-     * exist.
26
-     *
27
-     * @param string $name
28
-     * @throws Exception\NotFound
29
-     * @return INode
30
-     */
31
-    public function getChild($name) {
32
-
33
-        foreach ($this->getChildren() as $child) {
34
-
35
-            if ($child->getName() === $name) return $child;
36
-
37
-        }
38
-        throw new Exception\NotFound('File not found: ' . $name);
39
-
40
-    }
41
-
42
-    /**
43
-     * Checks is a child-node exists.
44
-     *
45
-     * It is generally a good idea to try and override this. Usually it can be optimized.
46
-     *
47
-     * @param string $name
48
-     * @return bool
49
-     */
50
-    public function childExists($name) {
51
-
52
-        try {
53
-
54
-            $this->getChild($name);
55
-            return true;
56
-
57
-        } catch (Exception\NotFound $e) {
58
-
59
-            return false;
60
-
61
-        }
62
-
63
-    }
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 succesful 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
-     * @return null|string
88
-     */
89
-    public function createFile($name, $data = null) {
90
-
91
-        throw new Exception\Forbidden('Permission denied to create file (filename ' . $name . ')');
92
-
93
-    }
94
-
95
-    /**
96
-     * Creates a new subdirectory
97
-     *
98
-     * @param string $name
99
-     * @throws Exception\Forbidden
100
-     * @return void
101
-     */
102
-    public function createDirectory($name) {
103
-
104
-        throw new Exception\Forbidden('Permission denied to create directory');
105
-
106
-    }
17
+	/**
18
+	 * Returns a child object, by its name.
19
+	 *
20
+	 * This method makes use of the getChildren method to grab all the child
21
+	 * nodes, and compares the name.
22
+	 * Generally its wise to override this, as this can usually be optimized
23
+	 *
24
+	 * This method must throw Sabre\DAV\Exception\NotFound if the node does not
25
+	 * exist.
26
+	 *
27
+	 * @param string $name
28
+	 * @throws Exception\NotFound
29
+	 * @return INode
30
+	 */
31
+	public function getChild($name) {
32
+
33
+		foreach ($this->getChildren() as $child) {
34
+
35
+			if ($child->getName() === $name) return $child;
36
+
37
+		}
38
+		throw new Exception\NotFound('File not found: ' . $name);
39
+
40
+	}
41
+
42
+	/**
43
+	 * Checks is a child-node exists.
44
+	 *
45
+	 * It is generally a good idea to try and override this. Usually it can be optimized.
46
+	 *
47
+	 * @param string $name
48
+	 * @return bool
49
+	 */
50
+	public function childExists($name) {
51
+
52
+		try {
53
+
54
+			$this->getChild($name);
55
+			return true;
56
+
57
+		} catch (Exception\NotFound $e) {
58
+
59
+			return false;
60
+
61
+		}
62
+
63
+	}
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 succesful 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
+	 * @return null|string
88
+	 */
89
+	public function createFile($name, $data = null) {
90
+
91
+		throw new Exception\Forbidden('Permission denied to create file (filename ' . $name . ')');
92
+
93
+	}
94
+
95
+	/**
96
+	 * Creates a new subdirectory
97
+	 *
98
+	 * @param string $name
99
+	 * @throws Exception\Forbidden
100
+	 * @return void
101
+	 */
102
+	public function createDirectory($name) {
103
+
104
+		throw new Exception\Forbidden('Permission denied to create directory');
105
+
106
+	}
107 107
 
108 108
 
109 109
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/DAV/Xml/Property/Href.php 4 patches
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -160,7 +160,7 @@
 block discarded – undo
160 160
     static function xmlDeserialize(Reader $reader) {
161 161
 
162 162
         $hrefs = [];
163
-        foreach ((array)$reader->parseInnerTree() as $elem) {
163
+        foreach ((array) $reader->parseInnerTree() as $elem) {
164 164
             if ($elem['name'] !== '{DAV:}href')
165 165
                 continue;
166 166
 
Please login to merge, or discard this patch.
Braces   +3 added lines, -2 removed lines patch added patch discarded remove patch
@@ -161,8 +161,9 @@
 block discarded – undo
161 161
 
162 162
         $hrefs = [];
163 163
         foreach ((array)$reader->parseInnerTree() as $elem) {
164
-            if ($elem['name'] !== '{DAV:}href')
165
-                continue;
164
+            if ($elem['name'] !== '{DAV:}href') {
165
+                            continue;
166
+            }
166 167
 
167 168
             $hrefs[] = $elem['value'];
168 169
 
Please login to merge, or discard this patch.
Indentation   +148 added lines, -148 removed lines patch added patch discarded remove patch
@@ -24,153 +24,153 @@
 block discarded – undo
24 24
  */
25 25
 class Href implements Element, HtmlOutput {
26 26
 
27
-    /**
28
-     * List of uris
29
-     *
30
-     * @var array
31
-     */
32
-    protected $hrefs;
33
-
34
-    /**
35
-     * Automatically prefix the url with the server base directory
36
-     *
37
-     * @var bool
38
-     */
39
-    protected $autoPrefix = true;
40
-
41
-    /**
42
-     * Constructor
43
-     *
44
-     * You must either pass a string for a single href, or an array of hrefs.
45
-     *
46
-     * If auto-prefix is set to false, the hrefs will be treated as absolute
47
-     * and not relative to the servers base uri.
48
-     *
49
-     * @param string|string[] $href
50
-     * @param bool $autoPrefix
51
-     */
52
-    public function __construct($hrefs, $autoPrefix = true) {
53
-
54
-        if (is_string($hrefs)) {
55
-            $hrefs = [$hrefs];
56
-        }
57
-        $this->hrefs = $hrefs;
58
-        $this->autoPrefix = $autoPrefix;
59
-
60
-
61
-    }
62
-
63
-    /**
64
-     * Returns the first Href.
65
-     *
66
-     * @return string
67
-     */
68
-    public function getHref() {
69
-
70
-        return $this->hrefs[0];
71
-
72
-    }
73
-
74
-    /**
75
-     * Returns the hrefs as an array
76
-     *
77
-     * @return array
78
-     */
79
-    public function getHrefs() {
80
-
81
-        return $this->hrefs;
82
-
83
-    }
84
-
85
-    /**
86
-     * The xmlSerialize metod is called during xml writing.
87
-     *
88
-     * Use the $writer argument to write its own xml serialization.
89
-     *
90
-     * An important note: do _not_ create a parent element. Any element
91
-     * implementing XmlSerializble should only ever write what's considered
92
-     * its 'inner xml'.
93
-     *
94
-     * The parent of the current element is responsible for writing a
95
-     * containing element.
96
-     *
97
-     * This allows serializers to be re-used for different element names.
98
-     *
99
-     * If you are opening new elements, you must also close them again.
100
-     *
101
-     * @param Writer $writer
102
-     * @return void
103
-     */
104
-    public function xmlSerialize(Writer $writer) {
105
-
106
-        foreach ($this->getHrefs() as $href) {
107
-            if ($this->autoPrefix) {
108
-                $href = $writer->contextUri . \Sabre\HTTP\encodePath($href);
109
-            }
110
-            $writer->writeElement('{DAV:}href', $href);
111
-        }
112
-
113
-    }
114
-
115
-    /**
116
-     * Generate html representation for this value.
117
-     *
118
-     * The html output is 100% trusted, and no effort is being made to sanitize
119
-     * it. It's up to the implementor to sanitize user provided values.
120
-     *
121
-     * The output must be in UTF-8.
122
-     *
123
-     * The baseUri parameter is a url to the root of the application, and can
124
-     * be used to construct local links.
125
-     *
126
-     * @param HtmlOutputHelper $html
127
-     * @return string
128
-     */
129
-    public function toHtml(HtmlOutputHelper $html) {
130
-
131
-        $links = [];
132
-        foreach ($this->getHrefs() as $href) {
133
-            $links[] = $html->link($href);
134
-        }
135
-        return implode('<br />', $links);
136
-
137
-    }
138
-
139
-    /**
140
-     * The deserialize method is called during xml parsing.
141
-     *
142
-     * This method is called statictly, this is because in theory this method
143
-     * may be used as a type of constructor, or factory method.
144
-     *
145
-     * Often you want to return an instance of the current class, but you are
146
-     * free to return other data as well.
147
-     *
148
-     * You are responsible for advancing the reader to the next element. Not
149
-     * doing anything will result in a never-ending loop.
150
-     *
151
-     * If you just want to skip parsing for this element altogether, you can
152
-     * just call $reader->next();
153
-     *
154
-     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
155
-     * the next element.
156
-     *
157
-     * @param Reader $reader
158
-     * @return mixed
159
-     */
160
-    static function xmlDeserialize(Reader $reader) {
161
-
162
-        $hrefs = [];
163
-        foreach ((array)$reader->parseInnerTree() as $elem) {
164
-            if ($elem['name'] !== '{DAV:}href')
165
-                continue;
166
-
167
-            $hrefs[] = $elem['value'];
168
-
169
-        }
170
-        if ($hrefs) {
171
-            return new self($hrefs, false);
172
-        }
173
-
174
-    }
27
+	/**
28
+	 * List of uris
29
+	 *
30
+	 * @var array
31
+	 */
32
+	protected $hrefs;
33
+
34
+	/**
35
+	 * Automatically prefix the url with the server base directory
36
+	 *
37
+	 * @var bool
38
+	 */
39
+	protected $autoPrefix = true;
40
+
41
+	/**
42
+	 * Constructor
43
+	 *
44
+	 * You must either pass a string for a single href, or an array of hrefs.
45
+	 *
46
+	 * If auto-prefix is set to false, the hrefs will be treated as absolute
47
+	 * and not relative to the servers base uri.
48
+	 *
49
+	 * @param string|string[] $href
50
+	 * @param bool $autoPrefix
51
+	 */
52
+	public function __construct($hrefs, $autoPrefix = true) {
53
+
54
+		if (is_string($hrefs)) {
55
+			$hrefs = [$hrefs];
56
+		}
57
+		$this->hrefs = $hrefs;
58
+		$this->autoPrefix = $autoPrefix;
59
+
60
+
61
+	}
62
+
63
+	/**
64
+	 * Returns the first Href.
65
+	 *
66
+	 * @return string
67
+	 */
68
+	public function getHref() {
69
+
70
+		return $this->hrefs[0];
71
+
72
+	}
73
+
74
+	/**
75
+	 * Returns the hrefs as an array
76
+	 *
77
+	 * @return array
78
+	 */
79
+	public function getHrefs() {
80
+
81
+		return $this->hrefs;
82
+
83
+	}
84
+
85
+	/**
86
+	 * The xmlSerialize metod is called during xml writing.
87
+	 *
88
+	 * Use the $writer argument to write its own xml serialization.
89
+	 *
90
+	 * An important note: do _not_ create a parent element. Any element
91
+	 * implementing XmlSerializble should only ever write what's considered
92
+	 * its 'inner xml'.
93
+	 *
94
+	 * The parent of the current element is responsible for writing a
95
+	 * containing element.
96
+	 *
97
+	 * This allows serializers to be re-used for different element names.
98
+	 *
99
+	 * If you are opening new elements, you must also close them again.
100
+	 *
101
+	 * @param Writer $writer
102
+	 * @return void
103
+	 */
104
+	public function xmlSerialize(Writer $writer) {
105
+
106
+		foreach ($this->getHrefs() as $href) {
107
+			if ($this->autoPrefix) {
108
+				$href = $writer->contextUri . \Sabre\HTTP\encodePath($href);
109
+			}
110
+			$writer->writeElement('{DAV:}href', $href);
111
+		}
112
+
113
+	}
114
+
115
+	/**
116
+	 * Generate html representation for this value.
117
+	 *
118
+	 * The html output is 100% trusted, and no effort is being made to sanitize
119
+	 * it. It's up to the implementor to sanitize user provided values.
120
+	 *
121
+	 * The output must be in UTF-8.
122
+	 *
123
+	 * The baseUri parameter is a url to the root of the application, and can
124
+	 * be used to construct local links.
125
+	 *
126
+	 * @param HtmlOutputHelper $html
127
+	 * @return string
128
+	 */
129
+	public function toHtml(HtmlOutputHelper $html) {
130
+
131
+		$links = [];
132
+		foreach ($this->getHrefs() as $href) {
133
+			$links[] = $html->link($href);
134
+		}
135
+		return implode('<br />', $links);
136
+
137
+	}
138
+
139
+	/**
140
+	 * The deserialize method is called during xml parsing.
141
+	 *
142
+	 * This method is called statictly, this is because in theory this method
143
+	 * may be used as a type of constructor, or factory method.
144
+	 *
145
+	 * Often you want to return an instance of the current class, but you are
146
+	 * free to return other data as well.
147
+	 *
148
+	 * You are responsible for advancing the reader to the next element. Not
149
+	 * doing anything will result in a never-ending loop.
150
+	 *
151
+	 * If you just want to skip parsing for this element altogether, you can
152
+	 * just call $reader->next();
153
+	 *
154
+	 * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
155
+	 * the next element.
156
+	 *
157
+	 * @param Reader $reader
158
+	 * @return mixed
159
+	 */
160
+	static function xmlDeserialize(Reader $reader) {
161
+
162
+		$hrefs = [];
163
+		foreach ((array)$reader->parseInnerTree() as $elem) {
164
+			if ($elem['name'] !== '{DAV:}href')
165
+				continue;
166
+
167
+			$hrefs[] = $elem['value'];
168
+
169
+		}
170
+		if ($hrefs) {
171
+			return new self($hrefs, false);
172
+		}
173
+
174
+	}
175 175
 
176 176
 }
Please login to merge, or discard this patch.
Doc Comments   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -46,7 +46,7 @@  discard block
 block discarded – undo
46 46
      * If auto-prefix is set to false, the hrefs will be treated as absolute
47 47
      * and not relative to the servers base uri.
48 48
      *
49
-     * @param string|string[] $href
49
+     * @param string|string[] $hrefs
50 50
      * @param bool $autoPrefix
51 51
      */
52 52
     public function __construct($hrefs, $autoPrefix = true) {
@@ -155,7 +155,7 @@  discard block
 block discarded – undo
155 155
      * the next element.
156 156
      *
157 157
      * @param Reader $reader
158
-     * @return mixed
158
+     * @return Href|null
159 159
      */
160 160
     static function xmlDeserialize(Reader $reader) {
161 161
 
Please login to merge, or discard this patch.
libraries/SabreDAV/DAV/Xml/Property/Complex.php 1 patch
Indentation   +58 added lines, -58 removed lines patch added patch discarded remove patch
@@ -17,73 +17,73 @@
 block discarded – undo
17 17
  */
18 18
 class Complex extends XmlFragment {
19 19
 
20
-    /**
21
-     * The deserialize method is called during xml parsing.
22
-     *
23
-     * This method is called statictly, this is because in theory this method
24
-     * may be used as a type of constructor, or factory method.
25
-     *
26
-     * Often you want to return an instance of the current class, but you are
27
-     * free to return other data as well.
28
-     *
29
-     * You are responsible for advancing the reader to the next element. Not
30
-     * doing anything will result in a never-ending loop.
31
-     *
32
-     * If you just want to skip parsing for this element altogether, you can
33
-     * just call $reader->next();
34
-     *
35
-     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
36
-     * the next element.
37
-     *
38
-     * @param Reader $reader
39
-     * @return mixed
40
-     */
41
-    static function xmlDeserialize(Reader $reader) {
20
+	/**
21
+	 * The deserialize method is called during xml parsing.
22
+	 *
23
+	 * This method is called statictly, this is because in theory this method
24
+	 * may be used as a type of constructor, or factory method.
25
+	 *
26
+	 * Often you want to return an instance of the current class, but you are
27
+	 * free to return other data as well.
28
+	 *
29
+	 * You are responsible for advancing the reader to the next element. Not
30
+	 * doing anything will result in a never-ending loop.
31
+	 *
32
+	 * If you just want to skip parsing for this element altogether, you can
33
+	 * just call $reader->next();
34
+	 *
35
+	 * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
36
+	 * the next element.
37
+	 *
38
+	 * @param Reader $reader
39
+	 * @return mixed
40
+	 */
41
+	static function xmlDeserialize(Reader $reader) {
42 42
 
43
-        $xml = $reader->readInnerXml();
43
+		$xml = $reader->readInnerXml();
44 44
 
45
-        if ($reader->nodeType === Reader::ELEMENT && $reader->isEmptyElement) {
46
-            // Easy!
47
-            $reader->next();
48
-            return null;
49
-        }
50
-        // Now we have a copy of the inner xml, we need to traverse it to get
51
-        // all the strings. If there's no non-string data, we just return the
52
-        // string, otherwise we return an instance of this class.
53
-        $reader->read();
45
+		if ($reader->nodeType === Reader::ELEMENT && $reader->isEmptyElement) {
46
+			// Easy!
47
+			$reader->next();
48
+			return null;
49
+		}
50
+		// Now we have a copy of the inner xml, we need to traverse it to get
51
+		// all the strings. If there's no non-string data, we just return the
52
+		// string, otherwise we return an instance of this class.
53
+		$reader->read();
54 54
 
55
-        $nonText = false;
56
-        $text = '';
55
+		$nonText = false;
56
+		$text = '';
57 57
 
58
-        while (true) {
58
+		while (true) {
59 59
 
60
-            switch ($reader->nodeType) {
61
-                case Reader::ELEMENT :
62
-                    $nonText = true;
63
-                    $reader->next();
64
-                    continue 2;
65
-                case Reader::TEXT :
66
-                case Reader::CDATA :
67
-                    $text .= $reader->value;
68
-                    break;
69
-                case Reader::END_ELEMENT :
70
-                    break 2;
71
-            }
72
-            $reader->read();
60
+			switch ($reader->nodeType) {
61
+				case Reader::ELEMENT :
62
+					$nonText = true;
63
+					$reader->next();
64
+					continue 2;
65
+				case Reader::TEXT :
66
+				case Reader::CDATA :
67
+					$text .= $reader->value;
68
+					break;
69
+				case Reader::END_ELEMENT :
70
+					break 2;
71
+			}
72
+			$reader->read();
73 73
 
74
-        }
74
+		}
75 75
 
76
-        // Make sure we advance the cursor one step further.
77
-        $reader->read();
76
+		// Make sure we advance the cursor one step further.
77
+		$reader->read();
78 78
 
79
-        if ($nonText) {
80
-            $new = new self($xml);
81
-            return $new;
82
-        } else {
83
-            return $text;
84
-        }
79
+		if ($nonText) {
80
+			$new = new self($xml);
81
+			return $new;
82
+		} else {
83
+			return $text;
84
+		}
85 85
 
86
-    }
86
+	}
87 87
 
88 88
 
89 89
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/DAV/Xml/Service.php 1 patch
Indentation   +32 added lines, -32 removed lines patch added patch discarded remove patch
@@ -11,37 +11,37 @@
 block discarded – undo
11 11
  */
12 12
 class Service extends \Sabre\Xml\Service {
13 13
 
14
-    /**
15
-     * This is a list of XML elements that we automatically map to PHP classes.
16
-     *
17
-     * For instance, this list may contain an entry `{DAV:}propfind` that would
18
-     * be mapped to Sabre\DAV\Xml\Request\PropFind
19
-     */
20
-    public $elementMap = [
21
-        '{DAV:}multistatus' => 'Sabre\\DAV\\Xml\\Response\\MultiStatus',
22
-        '{DAV:}response'    => 'Sabre\\DAV\\Xml\\Element\\Response',
23
-
24
-        // Requests
25
-        '{DAV:}propfind'       => 'Sabre\\DAV\\Xml\\Request\\PropFind',
26
-        '{DAV:}propertyupdate' => 'Sabre\\DAV\\Xml\\Request\\PropPatch',
27
-        '{DAV:}mkcol'          => 'Sabre\\DAV\\Xml\\Request\\MkCol',
28
-
29
-        // Properties
30
-        '{DAV:}resourcetype' => 'Sabre\\DAV\\Xml\\Property\\ResourceType',
31
-
32
-    ];
33
-
34
-    /**
35
-     * This is a default list of namespaces.
36
-     *
37
-     * If you are defining your own custom namespace, add it here to reduce
38
-     * bandwidth and improve legibility of xml bodies.
39
-     *
40
-     * @var array
41
-     */
42
-    public $namespaceMap = [
43
-        'DAV:'                   => 'd',
44
-        'http://sabredav.org/ns' => 's',
45
-    ];
14
+	/**
15
+	 * This is a list of XML elements that we automatically map to PHP classes.
16
+	 *
17
+	 * For instance, this list may contain an entry `{DAV:}propfind` that would
18
+	 * be mapped to Sabre\DAV\Xml\Request\PropFind
19
+	 */
20
+	public $elementMap = [
21
+		'{DAV:}multistatus' => 'Sabre\\DAV\\Xml\\Response\\MultiStatus',
22
+		'{DAV:}response'    => 'Sabre\\DAV\\Xml\\Element\\Response',
23
+
24
+		// Requests
25
+		'{DAV:}propfind'       => 'Sabre\\DAV\\Xml\\Request\\PropFind',
26
+		'{DAV:}propertyupdate' => 'Sabre\\DAV\\Xml\\Request\\PropPatch',
27
+		'{DAV:}mkcol'          => 'Sabre\\DAV\\Xml\\Request\\MkCol',
28
+
29
+		// Properties
30
+		'{DAV:}resourcetype' => 'Sabre\\DAV\\Xml\\Property\\ResourceType',
31
+
32
+	];
33
+
34
+	/**
35
+	 * This is a default list of namespaces.
36
+	 *
37
+	 * If you are defining your own custom namespace, add it here to reduce
38
+	 * bandwidth and improve legibility of xml bodies.
39
+	 *
40
+	 * @var array
41
+	 */
42
+	public $namespaceMap = [
43
+		'DAV:'                   => 'd',
44
+		'http://sabredav.org/ns' => 's',
45
+	];
46 46
 
47 47
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/CalDAV/Principal/User.php 2 patches
Braces   +6 added lines, -4 removed lines patch added patch discarded remove patch
@@ -57,11 +57,13 @@
 block discarded – undo
57 57
         if (!$principal) {
58 58
             throw new DAV\Exception\NotFound('Node with name ' . $name . ' was not found');
59 59
         }
60
-        if ($name === 'calendar-proxy-read')
61
-            return new ProxyRead($this->principalBackend, $this->principalProperties);
60
+        if ($name === 'calendar-proxy-read') {
61
+                    return new ProxyRead($this->principalBackend, $this->principalProperties);
62
+        }
62 63
 
63
-        if ($name === 'calendar-proxy-write')
64
-            return new ProxyWrite($this->principalBackend, $this->principalProperties);
64
+        if ($name === 'calendar-proxy-write') {
65
+                    return new ProxyWrite($this->principalBackend, $this->principalProperties);
66
+        }
65 67
 
66 68
         throw new DAV\Exception\NotFound('Node with name ' . $name . ' was not found');
67 69
 
Please login to merge, or discard this patch.
Indentation   +113 added lines, -113 removed lines patch added patch discarded remove patch
@@ -18,118 +18,118 @@
 block discarded – undo
18 18
  */
19 19
 class User extends DAVACL\Principal implements DAV\ICollection {
20 20
 
21
-    /**
22
-     * Creates a new file in the directory
23
-     *
24
-     * @param string $name Name of the file
25
-     * @param resource $data Initial payload, passed as a readable stream resource.
26
-     * @throws DAV\Exception\Forbidden
27
-     * @return void
28
-     */
29
-    public function createFile($name, $data = null) {
30
-
31
-        throw new DAV\Exception\Forbidden('Permission denied to create file (filename ' . $name . ')');
32
-
33
-    }
34
-
35
-    /**
36
-     * Creates a new subdirectory
37
-     *
38
-     * @param string $name
39
-     * @throws DAV\Exception\Forbidden
40
-     * @return void
41
-     */
42
-    public function createDirectory($name) {
43
-
44
-        throw new DAV\Exception\Forbidden('Permission denied to create directory');
45
-
46
-    }
47
-
48
-    /**
49
-     * Returns a specific child node, referenced by its name
50
-     *
51
-     * @param string $name
52
-     * @return DAV\INode
53
-     */
54
-    public function getChild($name) {
55
-
56
-        $principal = $this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/' . $name);
57
-        if (!$principal) {
58
-            throw new DAV\Exception\NotFound('Node with name ' . $name . ' was not found');
59
-        }
60
-        if ($name === 'calendar-proxy-read')
61
-            return new ProxyRead($this->principalBackend, $this->principalProperties);
62
-
63
-        if ($name === 'calendar-proxy-write')
64
-            return new ProxyWrite($this->principalBackend, $this->principalProperties);
65
-
66
-        throw new DAV\Exception\NotFound('Node with name ' . $name . ' was not found');
67
-
68
-    }
69
-
70
-    /**
71
-     * Returns an array with all the child nodes
72
-     *
73
-     * @return DAV\INode[]
74
-     */
75
-    public function getChildren() {
76
-
77
-        $r = [];
78
-        if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/calendar-proxy-read')) {
79
-            $r[] = new ProxyRead($this->principalBackend, $this->principalProperties);
80
-        }
81
-        if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/calendar-proxy-write')) {
82
-            $r[] = new ProxyWrite($this->principalBackend, $this->principalProperties);
83
-        }
84
-
85
-        return $r;
86
-
87
-    }
88
-
89
-    /**
90
-     * Returns whether or not the child node exists
91
-     *
92
-     * @param string $name
93
-     * @return bool
94
-     */
95
-    public function childExists($name) {
96
-
97
-        try {
98
-            $this->getChild($name);
99
-            return true;
100
-        } catch (DAV\Exception\NotFound $e) {
101
-            return false;
102
-        }
103
-
104
-    }
105
-
106
-    /**
107
-     * Returns a list of ACE's for this node.
108
-     *
109
-     * Each ACE has the following properties:
110
-     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
111
-     *     currently the only supported privileges
112
-     *   * 'principal', a url to the principal who owns the node
113
-     *   * 'protected' (optional), indicating that this ACE is not allowed to
114
-     *      be updated.
115
-     *
116
-     * @return array
117
-     */
118
-    public function getACL() {
119
-
120
-        $acl = parent::getACL();
121
-        $acl[] = [
122
-            'privilege' => '{DAV:}read',
123
-            'principal' => $this->principalProperties['uri'] . '/calendar-proxy-read',
124
-            'protected' => true,
125
-        ];
126
-        $acl[] = [
127
-            'privilege' => '{DAV:}read',
128
-            'principal' => $this->principalProperties['uri'] . '/calendar-proxy-write',
129
-            'protected' => true,
130
-        ];
131
-        return $acl;
132
-
133
-    }
21
+	/**
22
+	 * Creates a new file in the directory
23
+	 *
24
+	 * @param string $name Name of the file
25
+	 * @param resource $data Initial payload, passed as a readable stream resource.
26
+	 * @throws DAV\Exception\Forbidden
27
+	 * @return void
28
+	 */
29
+	public function createFile($name, $data = null) {
30
+
31
+		throw new DAV\Exception\Forbidden('Permission denied to create file (filename ' . $name . ')');
32
+
33
+	}
34
+
35
+	/**
36
+	 * Creates a new subdirectory
37
+	 *
38
+	 * @param string $name
39
+	 * @throws DAV\Exception\Forbidden
40
+	 * @return void
41
+	 */
42
+	public function createDirectory($name) {
43
+
44
+		throw new DAV\Exception\Forbidden('Permission denied to create directory');
45
+
46
+	}
47
+
48
+	/**
49
+	 * Returns a specific child node, referenced by its name
50
+	 *
51
+	 * @param string $name
52
+	 * @return DAV\INode
53
+	 */
54
+	public function getChild($name) {
55
+
56
+		$principal = $this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/' . $name);
57
+		if (!$principal) {
58
+			throw new DAV\Exception\NotFound('Node with name ' . $name . ' was not found');
59
+		}
60
+		if ($name === 'calendar-proxy-read')
61
+			return new ProxyRead($this->principalBackend, $this->principalProperties);
62
+
63
+		if ($name === 'calendar-proxy-write')
64
+			return new ProxyWrite($this->principalBackend, $this->principalProperties);
65
+
66
+		throw new DAV\Exception\NotFound('Node with name ' . $name . ' was not found');
67
+
68
+	}
69
+
70
+	/**
71
+	 * Returns an array with all the child nodes
72
+	 *
73
+	 * @return DAV\INode[]
74
+	 */
75
+	public function getChildren() {
76
+
77
+		$r = [];
78
+		if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/calendar-proxy-read')) {
79
+			$r[] = new ProxyRead($this->principalBackend, $this->principalProperties);
80
+		}
81
+		if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/calendar-proxy-write')) {
82
+			$r[] = new ProxyWrite($this->principalBackend, $this->principalProperties);
83
+		}
84
+
85
+		return $r;
86
+
87
+	}
88
+
89
+	/**
90
+	 * Returns whether or not the child node exists
91
+	 *
92
+	 * @param string $name
93
+	 * @return bool
94
+	 */
95
+	public function childExists($name) {
96
+
97
+		try {
98
+			$this->getChild($name);
99
+			return true;
100
+		} catch (DAV\Exception\NotFound $e) {
101
+			return false;
102
+		}
103
+
104
+	}
105
+
106
+	/**
107
+	 * Returns a list of ACE's for this node.
108
+	 *
109
+	 * Each ACE has the following properties:
110
+	 *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
111
+	 *     currently the only supported privileges
112
+	 *   * 'principal', a url to the principal who owns the node
113
+	 *   * 'protected' (optional), indicating that this ACE is not allowed to
114
+	 *      be updated.
115
+	 *
116
+	 * @return array
117
+	 */
118
+	public function getACL() {
119
+
120
+		$acl = parent::getACL();
121
+		$acl[] = [
122
+			'privilege' => '{DAV:}read',
123
+			'principal' => $this->principalProperties['uri'] . '/calendar-proxy-read',
124
+			'protected' => true,
125
+		];
126
+		$acl[] = [
127
+			'privilege' => '{DAV:}read',
128
+			'principal' => $this->principalProperties['uri'] . '/calendar-proxy-write',
129
+			'protected' => true,
130
+		];
131
+		return $acl;
132
+
133
+	}
134 134
 
135 135
 }
Please login to merge, or discard this patch.