Completed
Push — developer ( d7bfd9...1cf1f5 )
by Błażej
520:50 queued 478:32
created
libraries/SabreDAV/DAV/Locks/Backend/File.php 2 patches
Braces   +9 added lines, -3 removed lines patch added patch discarded remove patch
@@ -73,7 +73,9 @@  discard block
 block discarded – undo
73 73
 
74 74
         // Checking if we can remove any of these locks
75 75
         foreach ($newLocks as $k => $lock) {
76
-            if (time() > $lock->timeout + $lock->created) unset($newLocks[$k]);
76
+            if (time() > $lock->timeout + $lock->created) {
77
+            	unset($newLocks[$k]);
78
+            }
77 79
         }
78 80
         return $newLocks;
79 81
 
@@ -140,7 +142,9 @@  discard block
 block discarded – undo
140 142
      */
141 143
     protected function getData() {
142 144
 
143
-        if (!file_exists($this->locksFile)) return [];
145
+        if (!file_exists($this->locksFile)) {
146
+        	return [];
147
+        }
144 148
 
145 149
         // opening up the file, and creating a shared lock
146 150
         $handle = fopen($this->locksFile, 'r');
@@ -155,7 +159,9 @@  discard block
 block discarded – undo
155 159
 
156 160
         // Unserializing and checking if the resource file contains data for this file
157 161
         $data = unserialize($data);
158
-        if (!$data) return [];
162
+        if (!$data) {
163
+        	return [];
164
+        }
159 165
         return $data;
160 166
 
161 167
     }
Please login to merge, or discard this patch.
Indentation   +160 added lines, -160 removed lines patch added patch discarded remove patch
@@ -19,167 +19,167 @@
 block discarded – undo
19 19
  */
20 20
 class File extends AbstractBackend {
21 21
 
22
-    /**
23
-     * The storage file
24
-     *
25
-     * @var string
26
-     */
27
-    private $locksFile;
28
-
29
-    /**
30
-     * Constructor
31
-     *
32
-     * @param string $locksFile path to file
33
-     */
34
-    public function __construct($locksFile) {
35
-
36
-        $this->locksFile = $locksFile;
37
-
38
-    }
39
-
40
-    /**
41
-     * Returns a list of Sabre\DAV\Locks\LockInfo objects
42
-     *
43
-     * This method should return all the locks for a particular uri, including
44
-     * locks that might be set on a parent uri.
45
-     *
46
-     * If returnChildLocks is set to true, this method should also look for
47
-     * any locks in the subtree of the uri for locks.
48
-     *
49
-     * @param string $uri
50
-     * @param bool $returnChildLocks
51
-     * @return array
52
-     */
53
-    public function getLocks($uri, $returnChildLocks) {
54
-
55
-        $newLocks = [];
56
-
57
-        $locks = $this->getData();
58
-
59
-        foreach ($locks as $lock) {
60
-
61
-            if ($lock->uri === $uri ||
62
-                //deep locks on parents
63
-                ($lock->depth != 0 && strpos($uri, $lock->uri . '/') === 0) ||
64
-
65
-                // locks on children
66
-                ($returnChildLocks && (strpos($lock->uri, $uri . '/') === 0))) {
67
-
68
-                $newLocks[] = $lock;
69
-
70
-            }
71
-
72
-        }
73
-
74
-        // Checking if we can remove any of these locks
75
-        foreach ($newLocks as $k => $lock) {
76
-            if (time() > $lock->timeout + $lock->created) unset($newLocks[$k]);
77
-        }
78
-        return $newLocks;
79
-
80
-    }
81
-
82
-    /**
83
-     * Locks a uri
84
-     *
85
-     * @param string $uri
86
-     * @param LockInfo $lockInfo
87
-     * @return bool
88
-     */
89
-    public function lock($uri, LockInfo $lockInfo) {
90
-
91
-        // We're making the lock timeout 30 minutes
92
-        $lockInfo->timeout = 1800;
93
-        $lockInfo->created = time();
94
-        $lockInfo->uri = $uri;
95
-
96
-        $locks = $this->getData();
97
-
98
-        foreach ($locks as $k => $lock) {
99
-            if (
100
-                ($lock->token == $lockInfo->token) ||
101
-                (time() > $lock->timeout + $lock->created)
102
-            ) {
103
-                unset($locks[$k]);
104
-            }
105
-        }
106
-        $locks[] = $lockInfo;
107
-        $this->putData($locks);
108
-        return true;
109
-
110
-    }
111
-
112
-    /**
113
-     * Removes a lock from a uri
114
-     *
115
-     * @param string $uri
116
-     * @param LockInfo $lockInfo
117
-     * @return bool
118
-     */
119
-    public function unlock($uri, LockInfo $lockInfo) {
120
-
121
-        $locks = $this->getData();
122
-        foreach ($locks as $k => $lock) {
123
-
124
-            if ($lock->token == $lockInfo->token) {
125
-
126
-                unset($locks[$k]);
127
-                $this->putData($locks);
128
-                return true;
129
-
130
-            }
131
-        }
132
-        return false;
133
-
134
-    }
135
-
136
-    /**
137
-     * Loads the lockdata from the filesystem.
138
-     *
139
-     * @return array
140
-     */
141
-    protected function getData() {
142
-
143
-        if (!file_exists($this->locksFile)) return [];
144
-
145
-        // opening up the file, and creating a shared lock
146
-        $handle = fopen($this->locksFile, 'r');
147
-        flock($handle, LOCK_SH);
148
-
149
-        // Reading data until the eof
150
-        $data = stream_get_contents($handle);
22
+	/**
23
+	 * The storage file
24
+	 *
25
+	 * @var string
26
+	 */
27
+	private $locksFile;
28
+
29
+	/**
30
+	 * Constructor
31
+	 *
32
+	 * @param string $locksFile path to file
33
+	 */
34
+	public function __construct($locksFile) {
35
+
36
+		$this->locksFile = $locksFile;
37
+
38
+	}
39
+
40
+	/**
41
+	 * Returns a list of Sabre\DAV\Locks\LockInfo objects
42
+	 *
43
+	 * This method should return all the locks for a particular uri, including
44
+	 * locks that might be set on a parent uri.
45
+	 *
46
+	 * If returnChildLocks is set to true, this method should also look for
47
+	 * any locks in the subtree of the uri for locks.
48
+	 *
49
+	 * @param string $uri
50
+	 * @param bool $returnChildLocks
51
+	 * @return array
52
+	 */
53
+	public function getLocks($uri, $returnChildLocks) {
54
+
55
+		$newLocks = [];
56
+
57
+		$locks = $this->getData();
58
+
59
+		foreach ($locks as $lock) {
60
+
61
+			if ($lock->uri === $uri ||
62
+				//deep locks on parents
63
+				($lock->depth != 0 && strpos($uri, $lock->uri . '/') === 0) ||
64
+
65
+				// locks on children
66
+				($returnChildLocks && (strpos($lock->uri, $uri . '/') === 0))) {
67
+
68
+				$newLocks[] = $lock;
69
+
70
+			}
71
+
72
+		}
73
+
74
+		// Checking if we can remove any of these locks
75
+		foreach ($newLocks as $k => $lock) {
76
+			if (time() > $lock->timeout + $lock->created) unset($newLocks[$k]);
77
+		}
78
+		return $newLocks;
79
+
80
+	}
81
+
82
+	/**
83
+	 * Locks a uri
84
+	 *
85
+	 * @param string $uri
86
+	 * @param LockInfo $lockInfo
87
+	 * @return bool
88
+	 */
89
+	public function lock($uri, LockInfo $lockInfo) {
90
+
91
+		// We're making the lock timeout 30 minutes
92
+		$lockInfo->timeout = 1800;
93
+		$lockInfo->created = time();
94
+		$lockInfo->uri = $uri;
95
+
96
+		$locks = $this->getData();
97
+
98
+		foreach ($locks as $k => $lock) {
99
+			if (
100
+				($lock->token == $lockInfo->token) ||
101
+				(time() > $lock->timeout + $lock->created)
102
+			) {
103
+				unset($locks[$k]);
104
+			}
105
+		}
106
+		$locks[] = $lockInfo;
107
+		$this->putData($locks);
108
+		return true;
109
+
110
+	}
111
+
112
+	/**
113
+	 * Removes a lock from a uri
114
+	 *
115
+	 * @param string $uri
116
+	 * @param LockInfo $lockInfo
117
+	 * @return bool
118
+	 */
119
+	public function unlock($uri, LockInfo $lockInfo) {
120
+
121
+		$locks = $this->getData();
122
+		foreach ($locks as $k => $lock) {
123
+
124
+			if ($lock->token == $lockInfo->token) {
125
+
126
+				unset($locks[$k]);
127
+				$this->putData($locks);
128
+				return true;
129
+
130
+			}
131
+		}
132
+		return false;
133
+
134
+	}
135
+
136
+	/**
137
+	 * Loads the lockdata from the filesystem.
138
+	 *
139
+	 * @return array
140
+	 */
141
+	protected function getData() {
142
+
143
+		if (!file_exists($this->locksFile)) return [];
144
+
145
+		// opening up the file, and creating a shared lock
146
+		$handle = fopen($this->locksFile, 'r');
147
+		flock($handle, LOCK_SH);
148
+
149
+		// Reading data until the eof
150
+		$data = stream_get_contents($handle);
151 151
 
152
-        // We're all good
153
-        flock($handle, LOCK_UN);
154
-        fclose($handle);
152
+		// We're all good
153
+		flock($handle, LOCK_UN);
154
+		fclose($handle);
155 155
 
156
-        // Unserializing and checking if the resource file contains data for this file
157
-        $data = unserialize($data);
158
-        if (!$data) return [];
159
-        return $data;
160
-
161
-    }
162
-
163
-    /**
164
-     * Saves the lockdata
165
-     *
166
-     * @param array $newData
167
-     * @return void
168
-     */
169
-    protected function putData(array $newData) {
170
-
171
-        // opening up the file, and creating an exclusive lock
172
-        $handle = fopen($this->locksFile, 'a+');
173
-        flock($handle, LOCK_EX);
174
-
175
-        // We can only truncate and rewind once the lock is acquired.
176
-        ftruncate($handle, 0);
177
-        rewind($handle);
178
-
179
-        fwrite($handle, serialize($newData));
180
-        flock($handle, LOCK_UN);
181
-        fclose($handle);
182
-
183
-    }
156
+		// Unserializing and checking if the resource file contains data for this file
157
+		$data = unserialize($data);
158
+		if (!$data) return [];
159
+		return $data;
160
+
161
+	}
162
+
163
+	/**
164
+	 * Saves the lockdata
165
+	 *
166
+	 * @param array $newData
167
+	 * @return void
168
+	 */
169
+	protected function putData(array $newData) {
170
+
171
+		// opening up the file, and creating an exclusive lock
172
+		$handle = fopen($this->locksFile, 'a+');
173
+		flock($handle, LOCK_EX);
174
+
175
+		// We can only truncate and rewind once the lock is acquired.
176
+		ftruncate($handle, 0);
177
+		rewind($handle);
178
+
179
+		fwrite($handle, serialize($newData));
180
+		flock($handle, LOCK_UN);
181
+		fclose($handle);
182
+
183
+	}
184 184
 
185 185
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/DAV/Client.php 2 patches
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -229,7 +229,7 @@  discard block
 block discarded – undo
229 229
 
230 230
         $response = $this->send($request);
231 231
 
232
-        if ((int)$response->getStatus() >= 400) {
232
+        if ((int) $response->getStatus() >= 400) {
233 233
             throw new \Sabre\HTTP\ClientHttpException($response);
234 234
         }
235 235
 
@@ -372,7 +372,7 @@  discard block
 block discarded – undo
372 372
         $response = $this->send(new HTTP\Request($method, $url, $headers, $body));
373 373
         return [
374 374
             'body'       => $response->getBodyAsString(),
375
-            'statusCode' => (int)$response->getStatus(),
375
+            'statusCode' => (int) $response->getStatus(),
376 376
             'headers'    => array_change_key_case($response->getHeaders()),
377 377
         ];
378 378
 
Please login to merge, or discard this patch.
Indentation   +426 added lines, -426 removed lines patch added patch discarded remove patch
@@ -18,431 +18,431 @@
 block discarded – undo
18 18
  */
19 19
 class Client extends HTTP\Client {
20 20
 
21
-    /**
22
-     * The xml service.
23
-     *
24
-     * Uset this service to configure the property and namespace maps.
25
-     *
26
-     * @var mixed
27
-     */
28
-    public $xml;
29
-
30
-    /**
31
-     * The elementMap
32
-     *
33
-     * This property is linked via reference to $this->xml->elementMap.
34
-     * It's deprecated as of version 3.0.0, and should no longer be used.
35
-     *
36
-     * @deprecated
37
-     * @var array
38
-     */
39
-    public $propertyMap = [];
40
-
41
-    /**
42
-     * Base URI
43
-     *
44
-     * This URI will be used to resolve relative urls.
45
-     *
46
-     * @var string
47
-     */
48
-    protected $baseUri;
49
-
50
-    /**
51
-     * Basic authentication
52
-     */
53
-    const AUTH_BASIC = 1;
54
-
55
-    /**
56
-     * Digest authentication
57
-     */
58
-    const AUTH_DIGEST = 2;
59
-
60
-    /**
61
-     * NTLM authentication
62
-     */
63
-    const AUTH_NTLM = 4;
64
-
65
-    /**
66
-     * Identity encoding, which basically does not nothing.
67
-     */
68
-    const ENCODING_IDENTITY = 1;
69
-
70
-    /**
71
-     * Deflate encoding
72
-     */
73
-    const ENCODING_DEFLATE = 2;
74
-
75
-    /**
76
-     * Gzip encoding
77
-     */
78
-    const ENCODING_GZIP = 4;
79
-
80
-    /**
81
-     * Sends all encoding headers.
82
-     */
83
-    const ENCODING_ALL = 7;
84
-
85
-    /**
86
-     * Content-encoding
87
-     *
88
-     * @var int
89
-     */
90
-    protected $encoding = self::ENCODING_IDENTITY;
91
-
92
-    /**
93
-     * Constructor
94
-     *
95
-     * Settings are provided through the 'settings' argument. The following
96
-     * settings are supported:
97
-     *
98
-     *   * baseUri
99
-     *   * userName (optional)
100
-     *   * password (optional)
101
-     *   * proxy (optional)
102
-     *   * authType (optional)
103
-     *   * encoding (optional)
104
-     *
105
-     *  authType must be a bitmap, using self::AUTH_BASIC, self::AUTH_DIGEST
106
-     *  and self::AUTH_NTLM. If you know which authentication method will be
107
-     *  used, it's recommended to set it, as it will save a great deal of
108
-     *  requests to 'discover' this information.
109
-     *
110
-     *  Encoding is a bitmap with one of the ENCODING constants.
111
-     *
112
-     * @param array $settings
113
-     */
114
-    public function __construct(array $settings) {
115
-
116
-        if (!isset($settings['baseUri'])) {
117
-            throw new \InvalidArgumentException('A baseUri must be provided');
118
-        }
119
-
120
-        parent::__construct();
121
-
122
-        $this->baseUri = $settings['baseUri'];
123
-
124
-        if (isset($settings['proxy'])) {
125
-            $this->addCurlSetting(CURLOPT_PROXY, $settings['proxy']);
126
-        }
127
-
128
-        if (isset($settings['userName'])) {
129
-            $userName = $settings['userName'];
130
-            $password = isset($settings['password']) ? $settings['password'] : '';
131
-
132
-            if (isset($settings['authType'])) {
133
-                $curlType = 0;
134
-                if ($settings['authType'] & self::AUTH_BASIC) {
135
-                    $curlType |= CURLAUTH_BASIC;
136
-                }
137
-                if ($settings['authType'] & self::AUTH_DIGEST) {
138
-                    $curlType |= CURLAUTH_DIGEST;
139
-                }
140
-                if ($settings['authType'] & self::AUTH_NTLM) {
141
-                    $curlType |= CURLAUTH_NTLM;
142
-                }
143
-            } else {
144
-                $curlType = CURLAUTH_BASIC | CURLAUTH_DIGEST;
145
-            }
146
-
147
-            $this->addCurlSetting(CURLOPT_HTTPAUTH, $curlType);
148
-            $this->addCurlSetting(CURLOPT_USERPWD, $userName . ':' . $password);
149
-
150
-        }
151
-
152
-        if (isset($settings['encoding'])) {
153
-            $encoding = $settings['encoding'];
154
-
155
-            $encodings = [];
156
-            if ($encoding & self::ENCODING_IDENTITY) {
157
-                $encodings[] = 'identity';
158
-            }
159
-            if ($encoding & self::ENCODING_DEFLATE) {
160
-                $encodings[] = 'deflate';
161
-            }
162
-            if ($encoding & self::ENCODING_GZIP) {
163
-                $encodings[] = 'gzip';
164
-            }
165
-            $this->addCurlSetting(CURLOPT_ENCODING, implode(',', $encodings));
166
-        }
167
-
168
-        $this->addCurlSetting(CURLOPT_USERAGENT, 'sabre-dav/' . Version::VERSION . ' (http://sabre.io/)');
169
-
170
-        $this->xml = new Xml\Service();
171
-        // BC
172
-        $this->propertyMap = & $this->xml->elementMap;
173
-
174
-    }
175
-
176
-    /**
177
-     * Does a PROPFIND request
178
-     *
179
-     * The list of requested properties must be specified as an array, in clark
180
-     * notation.
181
-     *
182
-     * The returned array will contain a list of filenames as keys, and
183
-     * properties as values.
184
-     *
185
-     * The properties array will contain the list of properties. Only properties
186
-     * that are actually returned from the server (without error) will be
187
-     * returned, anything else is discarded.
188
-     *
189
-     * Depth should be either 0 or 1. A depth of 1 will cause a request to be
190
-     * made to the server to also return all child resources.
191
-     *
192
-     * @param string $url
193
-     * @param array $properties
194
-     * @param int $depth
195
-     * @return array
196
-     */
197
-    public function propFind($url, array $properties, $depth = 0) {
198
-
199
-        $dom = new \DOMDocument('1.0', 'UTF-8');
200
-        $dom->formatOutput = true;
201
-        $root = $dom->createElementNS('DAV:', 'd:propfind');
202
-        $prop = $dom->createElement('d:prop');
203
-
204
-        foreach ($properties as $property) {
205
-
206
-            list(
207
-                $namespace,
208
-                $elementName
209
-            ) = \Sabre\Xml\Service::parseClarkNotation($property);
210
-
211
-            if ($namespace === 'DAV:') {
212
-                $element = $dom->createElement('d:' . $elementName);
213
-            } else {
214
-                $element = $dom->createElementNS($namespace, 'x:' . $elementName);
215
-            }
216
-
217
-            $prop->appendChild($element);
218
-        }
219
-
220
-        $dom->appendChild($root)->appendChild($prop);
221
-        $body = $dom->saveXML();
222
-
223
-        $url = $this->getAbsoluteUrl($url);
224
-
225
-        $request = new HTTP\Request('PROPFIND', $url, [
226
-            'Depth'        => $depth,
227
-            'Content-Type' => 'application/xml'
228
-        ], $body);
229
-
230
-        $response = $this->send($request);
231
-
232
-        if ((int)$response->getStatus() >= 400) {
233
-            throw new \Sabre\HTTP\ClientHttpException($response);
234
-        }
235
-
236
-        $result = $this->parseMultiStatus($response->getBodyAsString());
237
-
238
-        // If depth was 0, we only return the top item
239
-        if ($depth === 0) {
240
-            reset($result);
241
-            $result = current($result);
242
-            return isset($result[200]) ? $result[200] : [];
243
-        }
244
-
245
-        $newResult = [];
246
-        foreach ($result as $href => $statusList) {
247
-
248
-            $newResult[$href] = isset($statusList[200]) ? $statusList[200] : [];
249
-
250
-        }
251
-
252
-        return $newResult;
253
-
254
-    }
255
-
256
-    /**
257
-     * Updates a list of properties on the server
258
-     *
259
-     * The list of properties must have clark-notation properties for the keys,
260
-     * and the actual (string) value for the value. If the value is null, an
261
-     * attempt is made to delete the property.
262
-     *
263
-     * @param string $url
264
-     * @param array $properties
265
-     * @return bool
266
-     */
267
-    public function propPatch($url, array $properties) {
268
-
269
-        $propPatch = new Xml\Request\PropPatch();
270
-        $propPatch->properties = $properties;
271
-        $xml = $this->xml->write(
272
-            '{DAV:}propertyupdate',
273
-            $propPatch
274
-        );
275
-
276
-        $url = $this->getAbsoluteUrl($url);
277
-        $request = new HTTP\Request('PROPPATCH', $url, [
278
-            'Content-Type' => 'application/xml',
279
-        ], $xml);
280
-        $response = $this->send($request);
281
-
282
-        if ($response->getStatus() >= 400) {
283
-            throw new \Sabre\HTTP\ClientHttpException($response);
284
-        }
285
-
286
-        if ($response->getStatus() === 207) {
287
-            // If it's a 207, the request could still have failed, but the
288
-            // information is hidden in the response body.
289
-            $result = $this->parseMultiStatus($response->getBodyAsString());
290
-
291
-            $errorProperties = [];
292
-            foreach ($result as $href => $statusList) {
293
-                foreach ($statusList as $status => $properties) {
294
-
295
-                    if ($status >= 400) {
296
-                        foreach ($properties as $propName => $propValue) {
297
-                            $errorProperties[] = $propName . ' (' . $status . ')';
298
-                        }
299
-                    }
300
-
301
-                }
302
-            }
303
-            if ($errorProperties) {
304
-
305
-                throw new \Sabre\HTTP\ClientException('PROPPATCH failed. The following properties errored: ' . implode(', ', $errorProperties));
306
-            }
307
-        }
308
-        return true;
309
-
310
-    }
311
-
312
-    /**
313
-     * Performs an HTTP options request
314
-     *
315
-     * This method returns all the features from the 'DAV:' header as an array.
316
-     * If there was no DAV header, or no contents this method will return an
317
-     * empty array.
318
-     *
319
-     * @return array
320
-     */
321
-    public function options() {
322
-
323
-        $request = new HTTP\Request('OPTIONS', $this->getAbsoluteUrl(''));
324
-        $response = $this->send($request);
325
-
326
-        $dav = $response->getHeader('Dav');
327
-        if (!$dav) {
328
-            return [];
329
-        }
330
-
331
-        $features = explode(',', $dav);
332
-        foreach ($features as &$v) {
333
-            $v = trim($v);
334
-        }
335
-        return $features;
336
-
337
-    }
338
-
339
-    /**
340
-     * Performs an actual HTTP request, and returns the result.
341
-     *
342
-     * If the specified url is relative, it will be expanded based on the base
343
-     * url.
344
-     *
345
-     * The returned array contains 3 keys:
346
-     *   * body - the response body
347
-     *   * httpCode - a HTTP code (200, 404, etc)
348
-     *   * headers - a list of response http headers. The header names have
349
-     *     been lowercased.
350
-     *
351
-     * For large uploads, it's highly recommended to specify body as a stream
352
-     * resource. You can easily do this by simply passing the result of
353
-     * fopen(..., 'r').
354
-     *
355
-     * This method will throw an exception if an HTTP error was received. Any
356
-     * HTTP status code above 399 is considered an error.
357
-     *
358
-     * Note that it is no longer recommended to use this method, use the send()
359
-     * method instead.
360
-     *
361
-     * @param string $method
362
-     * @param string $url
363
-     * @param string|resource|null $body
364
-     * @param array $headers
365
-     * @throws ClientException, in case a curl error occurred.
366
-     * @return array
367
-     */
368
-    public function request($method, $url = '', $body = null, array $headers = []) {
369
-
370
-        $url = $this->getAbsoluteUrl($url);
371
-
372
-        $response = $this->send(new HTTP\Request($method, $url, $headers, $body));
373
-        return [
374
-            'body'       => $response->getBodyAsString(),
375
-            'statusCode' => (int)$response->getStatus(),
376
-            'headers'    => array_change_key_case($response->getHeaders()),
377
-        ];
378
-
379
-    }
380
-
381
-    /**
382
-     * Returns the full url based on the given url (which may be relative). All
383
-     * urls are expanded based on the base url as given by the server.
384
-     *
385
-     * @param string $url
386
-     * @return string
387
-     */
388
-    public function getAbsoluteUrl($url) {
389
-
390
-        // If the url starts with http:// or https://, the url is already absolute.
391
-        if (preg_match('/^http(s?):\/\//', $url)) {
392
-            return $url;
393
-        }
394
-
395
-        // If the url starts with a slash, we must calculate the url based off
396
-        // the root of the base url.
397
-        if (strpos($url, '/') === 0) {
398
-            $parts = parse_url($this->baseUri);
399
-            return $parts['scheme'] . '://' . $parts['host'] . (isset($parts['port']) ? ':' . $parts['port'] : '') . $url;
400
-        }
401
-
402
-        // Otherwise...
403
-        return $this->baseUri . $url;
404
-
405
-    }
406
-
407
-    /**
408
-     * Parses a WebDAV multistatus response body
409
-     *
410
-     * This method returns an array with the following structure
411
-     *
412
-     * [
413
-     *   'url/to/resource' => [
414
-     *     '200' => [
415
-     *        '{DAV:}property1' => 'value1',
416
-     *        '{DAV:}property2' => 'value2',
417
-     *     ],
418
-     *     '404' => [
419
-     *        '{DAV:}property1' => null,
420
-     *        '{DAV:}property2' => null,
421
-     *     ],
422
-     *   ],
423
-     *   'url/to/resource2' => [
424
-     *      .. etc ..
425
-     *   ]
426
-     * ]
427
-     *
428
-     *
429
-     * @param string $body xml body
430
-     * @return array
431
-     */
432
-    public function parseMultiStatus($body) {
433
-
434
-        $multistatus = $this->xml->expect('{DAV:}multistatus', $body);
435
-
436
-        $result = [];
437
-
438
-        foreach ($multistatus->getResponses() as $response) {
439
-
440
-            $result[$response->getHref()] = $response->getResponseProperties();
441
-
442
-        }
443
-
444
-        return $result;
445
-
446
-    }
21
+	/**
22
+	 * The xml service.
23
+	 *
24
+	 * Uset this service to configure the property and namespace maps.
25
+	 *
26
+	 * @var mixed
27
+	 */
28
+	public $xml;
29
+
30
+	/**
31
+	 * The elementMap
32
+	 *
33
+	 * This property is linked via reference to $this->xml->elementMap.
34
+	 * It's deprecated as of version 3.0.0, and should no longer be used.
35
+	 *
36
+	 * @deprecated
37
+	 * @var array
38
+	 */
39
+	public $propertyMap = [];
40
+
41
+	/**
42
+	 * Base URI
43
+	 *
44
+	 * This URI will be used to resolve relative urls.
45
+	 *
46
+	 * @var string
47
+	 */
48
+	protected $baseUri;
49
+
50
+	/**
51
+	 * Basic authentication
52
+	 */
53
+	const AUTH_BASIC = 1;
54
+
55
+	/**
56
+	 * Digest authentication
57
+	 */
58
+	const AUTH_DIGEST = 2;
59
+
60
+	/**
61
+	 * NTLM authentication
62
+	 */
63
+	const AUTH_NTLM = 4;
64
+
65
+	/**
66
+	 * Identity encoding, which basically does not nothing.
67
+	 */
68
+	const ENCODING_IDENTITY = 1;
69
+
70
+	/**
71
+	 * Deflate encoding
72
+	 */
73
+	const ENCODING_DEFLATE = 2;
74
+
75
+	/**
76
+	 * Gzip encoding
77
+	 */
78
+	const ENCODING_GZIP = 4;
79
+
80
+	/**
81
+	 * Sends all encoding headers.
82
+	 */
83
+	const ENCODING_ALL = 7;
84
+
85
+	/**
86
+	 * Content-encoding
87
+	 *
88
+	 * @var int
89
+	 */
90
+	protected $encoding = self::ENCODING_IDENTITY;
91
+
92
+	/**
93
+	 * Constructor
94
+	 *
95
+	 * Settings are provided through the 'settings' argument. The following
96
+	 * settings are supported:
97
+	 *
98
+	 *   * baseUri
99
+	 *   * userName (optional)
100
+	 *   * password (optional)
101
+	 *   * proxy (optional)
102
+	 *   * authType (optional)
103
+	 *   * encoding (optional)
104
+	 *
105
+	 *  authType must be a bitmap, using self::AUTH_BASIC, self::AUTH_DIGEST
106
+	 *  and self::AUTH_NTLM. If you know which authentication method will be
107
+	 *  used, it's recommended to set it, as it will save a great deal of
108
+	 *  requests to 'discover' this information.
109
+	 *
110
+	 *  Encoding is a bitmap with one of the ENCODING constants.
111
+	 *
112
+	 * @param array $settings
113
+	 */
114
+	public function __construct(array $settings) {
115
+
116
+		if (!isset($settings['baseUri'])) {
117
+			throw new \InvalidArgumentException('A baseUri must be provided');
118
+		}
119
+
120
+		parent::__construct();
121
+
122
+		$this->baseUri = $settings['baseUri'];
123
+
124
+		if (isset($settings['proxy'])) {
125
+			$this->addCurlSetting(CURLOPT_PROXY, $settings['proxy']);
126
+		}
127
+
128
+		if (isset($settings['userName'])) {
129
+			$userName = $settings['userName'];
130
+			$password = isset($settings['password']) ? $settings['password'] : '';
131
+
132
+			if (isset($settings['authType'])) {
133
+				$curlType = 0;
134
+				if ($settings['authType'] & self::AUTH_BASIC) {
135
+					$curlType |= CURLAUTH_BASIC;
136
+				}
137
+				if ($settings['authType'] & self::AUTH_DIGEST) {
138
+					$curlType |= CURLAUTH_DIGEST;
139
+				}
140
+				if ($settings['authType'] & self::AUTH_NTLM) {
141
+					$curlType |= CURLAUTH_NTLM;
142
+				}
143
+			} else {
144
+				$curlType = CURLAUTH_BASIC | CURLAUTH_DIGEST;
145
+			}
146
+
147
+			$this->addCurlSetting(CURLOPT_HTTPAUTH, $curlType);
148
+			$this->addCurlSetting(CURLOPT_USERPWD, $userName . ':' . $password);
149
+
150
+		}
151
+
152
+		if (isset($settings['encoding'])) {
153
+			$encoding = $settings['encoding'];
154
+
155
+			$encodings = [];
156
+			if ($encoding & self::ENCODING_IDENTITY) {
157
+				$encodings[] = 'identity';
158
+			}
159
+			if ($encoding & self::ENCODING_DEFLATE) {
160
+				$encodings[] = 'deflate';
161
+			}
162
+			if ($encoding & self::ENCODING_GZIP) {
163
+				$encodings[] = 'gzip';
164
+			}
165
+			$this->addCurlSetting(CURLOPT_ENCODING, implode(',', $encodings));
166
+		}
167
+
168
+		$this->addCurlSetting(CURLOPT_USERAGENT, 'sabre-dav/' . Version::VERSION . ' (http://sabre.io/)');
169
+
170
+		$this->xml = new Xml\Service();
171
+		// BC
172
+		$this->propertyMap = & $this->xml->elementMap;
173
+
174
+	}
175
+
176
+	/**
177
+	 * Does a PROPFIND request
178
+	 *
179
+	 * The list of requested properties must be specified as an array, in clark
180
+	 * notation.
181
+	 *
182
+	 * The returned array will contain a list of filenames as keys, and
183
+	 * properties as values.
184
+	 *
185
+	 * The properties array will contain the list of properties. Only properties
186
+	 * that are actually returned from the server (without error) will be
187
+	 * returned, anything else is discarded.
188
+	 *
189
+	 * Depth should be either 0 or 1. A depth of 1 will cause a request to be
190
+	 * made to the server to also return all child resources.
191
+	 *
192
+	 * @param string $url
193
+	 * @param array $properties
194
+	 * @param int $depth
195
+	 * @return array
196
+	 */
197
+	public function propFind($url, array $properties, $depth = 0) {
198
+
199
+		$dom = new \DOMDocument('1.0', 'UTF-8');
200
+		$dom->formatOutput = true;
201
+		$root = $dom->createElementNS('DAV:', 'd:propfind');
202
+		$prop = $dom->createElement('d:prop');
203
+
204
+		foreach ($properties as $property) {
205
+
206
+			list(
207
+				$namespace,
208
+				$elementName
209
+			) = \Sabre\Xml\Service::parseClarkNotation($property);
210
+
211
+			if ($namespace === 'DAV:') {
212
+				$element = $dom->createElement('d:' . $elementName);
213
+			} else {
214
+				$element = $dom->createElementNS($namespace, 'x:' . $elementName);
215
+			}
216
+
217
+			$prop->appendChild($element);
218
+		}
219
+
220
+		$dom->appendChild($root)->appendChild($prop);
221
+		$body = $dom->saveXML();
222
+
223
+		$url = $this->getAbsoluteUrl($url);
224
+
225
+		$request = new HTTP\Request('PROPFIND', $url, [
226
+			'Depth'        => $depth,
227
+			'Content-Type' => 'application/xml'
228
+		], $body);
229
+
230
+		$response = $this->send($request);
231
+
232
+		if ((int)$response->getStatus() >= 400) {
233
+			throw new \Sabre\HTTP\ClientHttpException($response);
234
+		}
235
+
236
+		$result = $this->parseMultiStatus($response->getBodyAsString());
237
+
238
+		// If depth was 0, we only return the top item
239
+		if ($depth === 0) {
240
+			reset($result);
241
+			$result = current($result);
242
+			return isset($result[200]) ? $result[200] : [];
243
+		}
244
+
245
+		$newResult = [];
246
+		foreach ($result as $href => $statusList) {
247
+
248
+			$newResult[$href] = isset($statusList[200]) ? $statusList[200] : [];
249
+
250
+		}
251
+
252
+		return $newResult;
253
+
254
+	}
255
+
256
+	/**
257
+	 * Updates a list of properties on the server
258
+	 *
259
+	 * The list of properties must have clark-notation properties for the keys,
260
+	 * and the actual (string) value for the value. If the value is null, an
261
+	 * attempt is made to delete the property.
262
+	 *
263
+	 * @param string $url
264
+	 * @param array $properties
265
+	 * @return bool
266
+	 */
267
+	public function propPatch($url, array $properties) {
268
+
269
+		$propPatch = new Xml\Request\PropPatch();
270
+		$propPatch->properties = $properties;
271
+		$xml = $this->xml->write(
272
+			'{DAV:}propertyupdate',
273
+			$propPatch
274
+		);
275
+
276
+		$url = $this->getAbsoluteUrl($url);
277
+		$request = new HTTP\Request('PROPPATCH', $url, [
278
+			'Content-Type' => 'application/xml',
279
+		], $xml);
280
+		$response = $this->send($request);
281
+
282
+		if ($response->getStatus() >= 400) {
283
+			throw new \Sabre\HTTP\ClientHttpException($response);
284
+		}
285
+
286
+		if ($response->getStatus() === 207) {
287
+			// If it's a 207, the request could still have failed, but the
288
+			// information is hidden in the response body.
289
+			$result = $this->parseMultiStatus($response->getBodyAsString());
290
+
291
+			$errorProperties = [];
292
+			foreach ($result as $href => $statusList) {
293
+				foreach ($statusList as $status => $properties) {
294
+
295
+					if ($status >= 400) {
296
+						foreach ($properties as $propName => $propValue) {
297
+							$errorProperties[] = $propName . ' (' . $status . ')';
298
+						}
299
+					}
300
+
301
+				}
302
+			}
303
+			if ($errorProperties) {
304
+
305
+				throw new \Sabre\HTTP\ClientException('PROPPATCH failed. The following properties errored: ' . implode(', ', $errorProperties));
306
+			}
307
+		}
308
+		return true;
309
+
310
+	}
311
+
312
+	/**
313
+	 * Performs an HTTP options request
314
+	 *
315
+	 * This method returns all the features from the 'DAV:' header as an array.
316
+	 * If there was no DAV header, or no contents this method will return an
317
+	 * empty array.
318
+	 *
319
+	 * @return array
320
+	 */
321
+	public function options() {
322
+
323
+		$request = new HTTP\Request('OPTIONS', $this->getAbsoluteUrl(''));
324
+		$response = $this->send($request);
325
+
326
+		$dav = $response->getHeader('Dav');
327
+		if (!$dav) {
328
+			return [];
329
+		}
330
+
331
+		$features = explode(',', $dav);
332
+		foreach ($features as &$v) {
333
+			$v = trim($v);
334
+		}
335
+		return $features;
336
+
337
+	}
338
+
339
+	/**
340
+	 * Performs an actual HTTP request, and returns the result.
341
+	 *
342
+	 * If the specified url is relative, it will be expanded based on the base
343
+	 * url.
344
+	 *
345
+	 * The returned array contains 3 keys:
346
+	 *   * body - the response body
347
+	 *   * httpCode - a HTTP code (200, 404, etc)
348
+	 *   * headers - a list of response http headers. The header names have
349
+	 *     been lowercased.
350
+	 *
351
+	 * For large uploads, it's highly recommended to specify body as a stream
352
+	 * resource. You can easily do this by simply passing the result of
353
+	 * fopen(..., 'r').
354
+	 *
355
+	 * This method will throw an exception if an HTTP error was received. Any
356
+	 * HTTP status code above 399 is considered an error.
357
+	 *
358
+	 * Note that it is no longer recommended to use this method, use the send()
359
+	 * method instead.
360
+	 *
361
+	 * @param string $method
362
+	 * @param string $url
363
+	 * @param string|resource|null $body
364
+	 * @param array $headers
365
+	 * @throws ClientException, in case a curl error occurred.
366
+	 * @return array
367
+	 */
368
+	public function request($method, $url = '', $body = null, array $headers = []) {
369
+
370
+		$url = $this->getAbsoluteUrl($url);
371
+
372
+		$response = $this->send(new HTTP\Request($method, $url, $headers, $body));
373
+		return [
374
+			'body'       => $response->getBodyAsString(),
375
+			'statusCode' => (int)$response->getStatus(),
376
+			'headers'    => array_change_key_case($response->getHeaders()),
377
+		];
378
+
379
+	}
380
+
381
+	/**
382
+	 * Returns the full url based on the given url (which may be relative). All
383
+	 * urls are expanded based on the base url as given by the server.
384
+	 *
385
+	 * @param string $url
386
+	 * @return string
387
+	 */
388
+	public function getAbsoluteUrl($url) {
389
+
390
+		// If the url starts with http:// or https://, the url is already absolute.
391
+		if (preg_match('/^http(s?):\/\//', $url)) {
392
+			return $url;
393
+		}
394
+
395
+		// If the url starts with a slash, we must calculate the url based off
396
+		// the root of the base url.
397
+		if (strpos($url, '/') === 0) {
398
+			$parts = parse_url($this->baseUri);
399
+			return $parts['scheme'] . '://' . $parts['host'] . (isset($parts['port']) ? ':' . $parts['port'] : '') . $url;
400
+		}
401
+
402
+		// Otherwise...
403
+		return $this->baseUri . $url;
404
+
405
+	}
406
+
407
+	/**
408
+	 * Parses a WebDAV multistatus response body
409
+	 *
410
+	 * This method returns an array with the following structure
411
+	 *
412
+	 * [
413
+	 *   'url/to/resource' => [
414
+	 *     '200' => [
415
+	 *        '{DAV:}property1' => 'value1',
416
+	 *        '{DAV:}property2' => 'value2',
417
+	 *     ],
418
+	 *     '404' => [
419
+	 *        '{DAV:}property1' => null,
420
+	 *        '{DAV:}property2' => null,
421
+	 *     ],
422
+	 *   ],
423
+	 *   'url/to/resource2' => [
424
+	 *      .. etc ..
425
+	 *   ]
426
+	 * ]
427
+	 *
428
+	 *
429
+	 * @param string $body xml body
430
+	 * @return array
431
+	 */
432
+	public function parseMultiStatus($body) {
433
+
434
+		$multistatus = $this->xml->expect('{DAV:}multistatus', $body);
435
+
436
+		$result = [];
437
+
438
+		foreach ($multistatus->getResponses() as $response) {
439
+
440
+			$result[$response->getHref()] = $response->getResponseProperties();
441
+
442
+		}
443
+
444
+		return $result;
445
+
446
+	}
447 447
 
448 448
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/DAV/Server.php 3 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.
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 3 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.
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/Browser/Plugin.php 3 patches
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -317,7 +317,7 @@  discard block
 block discarded – undo
317 317
 
318 318
                 $buttonActions = '';
319 319
                 if ($subProps['subNode'] instanceof DAV\IFile) {
320
-                    $buttonActions =  '<a href="' . $this->escapeHTML($subProps['fullPath']) . '?sabreAction=info"><span class="oi" data-glyph="info"></span></a>';
320
+                    $buttonActions = '<a href="' . $this->escapeHTML($subProps['fullPath']) . '?sabreAction=info"><span class="oi" data-glyph="info"></span></a>';
321 321
                 }
322 322
                 $this->server->emit('browserButtonActions', [$subProps['fullPath'], $subProps['subNode'], &$buttonActions]);
323 323
 
@@ -450,7 +450,7 @@  discard block
 block discarded – undo
450 450
 HTML;
451 451
 
452 452
         // If the path is empty, there's no parent.
453
-        if ($path)  {
453
+        if ($path) {
454 454
             list($parentUri) = URLUtil::splitPath($path);
455 455
             $fullPath = $this->server->getBaseUri() . URLUtil::encodePath($parentUri);
456 456
             $html .= '<a href="' . $fullPath . '" class="btn">⇤ Go to parent</a>';
Please login to merge, or discard this patch.
Indentation   +723 added lines, -723 removed lines patch added patch discarded remove patch
@@ -23,413 +23,413 @@  discard block
 block discarded – undo
23 23
  */
24 24
 class Plugin extends DAV\ServerPlugin {
25 25
 
26
-    /**
27
-     * reference to server class
28
-     *
29
-     * @var Sabre\DAV\Server
30
-     */
31
-    protected $server;
32
-
33
-    /**
34
-     * enablePost turns on the 'actions' panel, which allows people to create
35
-     * folders and upload files straight from a browser.
36
-     *
37
-     * @var bool
38
-     */
39
-    protected $enablePost = true;
40
-
41
-    /**
42
-     * A list of properties that are usually not interesting. This can cut down
43
-     * the browser output a bit by removing the properties that most people
44
-     * will likely not want to see.
45
-     *
46
-     * @var array
47
-     */
48
-    public $uninterestingProperties = [
49
-        '{DAV:}supportedlock',
50
-        '{DAV:}acl-restrictions',
51
-        '{DAV:}supported-privilege-set',
52
-        '{DAV:}supported-method-set',
53
-    ];
54
-
55
-    /**
56
-     * Creates the object.
57
-     *
58
-     * By default it will allow file creation and uploads.
59
-     * Specify the first argument as false to disable this
60
-     *
61
-     * @param bool $enablePost
62
-     */
63
-    public function __construct($enablePost = true) {
64
-
65
-        $this->enablePost = $enablePost;
66
-
67
-    }
68
-
69
-    /**
70
-     * Initializes the plugin and subscribes to events
71
-     *
72
-     * @param DAV\Server $server
73
-     * @return void
74
-     */
75
-    public function initialize(DAV\Server $server) {
76
-
77
-        $this->server = $server;
78
-        $this->server->on('method:GET', [$this, 'httpGetEarly'], 90);
79
-        $this->server->on('method:GET', [$this, 'httpGet'], 200);
80
-        $this->server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel'], 200);
81
-        if ($this->enablePost) $this->server->on('method:POST', [$this, 'httpPOST']);
82
-    }
83
-
84
-    /**
85
-     * This method intercepts GET requests that have ?sabreAction=info
86
-     * appended to the URL
87
-     *
88
-     * @param RequestInterface $request
89
-     * @param ResponseInterface $response
90
-     * @return bool
91
-     */
92
-    public function httpGetEarly(RequestInterface $request, ResponseInterface $response) {
93
-
94
-        $params = $request->getQueryParameters();
95
-        if (isset($params['sabreAction']) && $params['sabreAction'] === 'info') {
96
-            return $this->httpGet($request, $response);
97
-        }
98
-
99
-    }
100
-
101
-    /**
102
-     * This method intercepts GET requests to collections and returns the html
103
-     *
104
-     * @param RequestInterface $request
105
-     * @param ResponseInterface $response
106
-     * @return bool
107
-     */
108
-    public function httpGet(RequestInterface $request, ResponseInterface $response) {
109
-
110
-        // We're not using straight-up $_GET, because we want everything to be
111
-        // unit testable.
112
-        $getVars = $request->getQueryParameters();
113
-
114
-        // CSP headers
115
-        $this->server->httpResponse->setHeader('Content-Security-Policy', "img-src 'self'; style-src 'self';");
116
-
117
-        $sabreAction = isset($getVars['sabreAction']) ? $getVars['sabreAction'] : null;
118
-
119
-        switch ($sabreAction) {
120
-
121
-            case 'asset' :
122
-                // Asset handling, such as images
123
-                $this->serveAsset(isset($getVars['assetName']) ? $getVars['assetName'] : null);
124
-                return false;
125
-            default :
126
-            case 'info' :
127
-                try {
128
-                    $this->server->tree->getNodeForPath($request->getPath());
129
-                } catch (DAV\Exception\NotFound $e) {
130
-                    // We're simply stopping when the file isn't found to not interfere
131
-                    // with other plugins.
132
-                    return;
133
-                }
134
-
135
-                $response->setStatus(200);
136
-                $response->setHeader('Content-Type', 'text/html; charset=utf-8');
137
-
138
-                $response->setBody(
139
-                    $this->generateDirectoryIndex($request->getPath())
140
-                );
141
-
142
-                return false;
143
-
144
-            case 'plugins' :
145
-                $response->setStatus(200);
146
-                $response->setHeader('Content-Type', 'text/html; charset=utf-8');
147
-
148
-                $response->setBody(
149
-                    $this->generatePluginListing()
150
-                );
151
-
152
-                return false;
153
-
154
-        }
155
-
156
-    }
157
-
158
-    /**
159
-     * Handles POST requests for tree operations.
160
-     *
161
-     * @param RequestInterface $request
162
-     * @param ResponseInterface $response
163
-     * @return bool
164
-     */
165
-    public function httpPOST(RequestInterface $request, ResponseInterface $response) {
166
-
167
-        $contentType = $request->getHeader('Content-Type');
168
-        list($contentType) = explode(';', $contentType);
169
-        if ($contentType !== 'application/x-www-form-urlencoded' &&
170
-            $contentType !== 'multipart/form-data') {
171
-                return;
172
-        }
173
-        $postVars = $request->getPostData();
174
-
175
-        if (!isset($postVars['sabreAction']))
176
-            return;
177
-
178
-        $uri = $request->getPath();
179
-
180
-        if ($this->server->emit('onBrowserPostAction', [$uri, $postVars['sabreAction'], $postVars])) {
181
-
182
-            switch ($postVars['sabreAction']) {
183
-
184
-                case 'mkcol' :
185
-                    if (isset($postVars['name']) && trim($postVars['name'])) {
186
-                        // Using basename() because we won't allow slashes
187
-                        list(, $folderName) = URLUtil::splitPath(trim($postVars['name']));
188
-
189
-                        if (isset($postVars['resourceType'])) {
190
-                            $resourceType = explode(',', $postVars['resourceType']);
191
-                        } else {
192
-                            $resourceType = ['{DAV:}collection'];
193
-                        }
194
-
195
-                        $properties = [];
196
-                        foreach ($postVars as $varName => $varValue) {
197
-                            // Any _POST variable in clark notation is treated
198
-                            // like a property.
199
-                            if ($varName[0] === '{') {
200
-                                // PHP will convert any dots to underscores.
201
-                                // This leaves us with no way to differentiate
202
-                                // the two.
203
-                                // Therefore we replace the string *DOT* with a
204
-                                // real dot. * is not allowed in uris so we
205
-                                // should be good.
206
-                                $varName = str_replace('*DOT*', '.', $varName);
207
-                                $properties[$varName] = $varValue;
208
-                            }
209
-                        }
210
-
211
-                        $mkCol = new MkCol(
212
-                            $resourceType,
213
-                            $properties
214
-                        );
215
-                        $this->server->createCollection($uri . '/' . $folderName, $mkCol);
216
-                    }
217
-                    break;
218
-
219
-                // @codeCoverageIgnoreStart
220
-                case 'put' :
221
-
222
-                    if ($_FILES) $file = current($_FILES);
223
-                    else break;
224
-
225
-                    list(, $newName) = URLUtil::splitPath(trim($file['name']));
226
-                    if (isset($postVars['name']) && trim($postVars['name']))
227
-                        $newName = trim($postVars['name']);
228
-
229
-                    // Making sure we only have a 'basename' component
230
-                    list(, $newName) = URLUtil::splitPath($newName);
231
-
232
-                    if (is_uploaded_file($file['tmp_name'])) {
233
-                        $this->server->createFile($uri . '/' . $newName, fopen($file['tmp_name'], 'r'));
234
-                    }
235
-                    break;
236
-                // @codeCoverageIgnoreEnd
237
-
238
-            }
239
-
240
-        }
241
-        $response->setHeader('Location', $request->getUrl());
242
-        $response->setStatus(302);
243
-        return false;
244
-
245
-    }
246
-
247
-    /**
248
-     * Escapes a string for html.
249
-     *
250
-     * @param string $value
251
-     * @return string
252
-     */
253
-    public function escapeHTML($value) {
254
-
255
-        return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
256
-
257
-    }
258
-
259
-    /**
260
-     * Generates the html directory index for a given url
261
-     *
262
-     * @param string $path
263
-     * @return string
264
-     */
265
-    public function generateDirectoryIndex($path) {
266
-
267
-        $html = $this->generateHeader($path ? $path : '/', $path);
268
-
269
-        $node = $this->server->tree->getNodeForPath($path);
270
-        if ($node instanceof DAV\ICollection) {
271
-
272
-            $html .= "<section><h1>Nodes</h1>\n";
273
-            $html .= "<table class=\"nodeTable\">";
274
-
275
-            $subNodes = $this->server->getPropertiesForChildren($path, [
276
-                '{DAV:}displayname',
277
-                '{DAV:}resourcetype',
278
-                '{DAV:}getcontenttype',
279
-                '{DAV:}getcontentlength',
280
-                '{DAV:}getlastmodified',
281
-            ]);
282
-
283
-            foreach ($subNodes as $subPath => $subProps) {
284
-
285
-                $subNode = $this->server->tree->getNodeForPath($subPath);
286
-                $fullPath = $this->server->getBaseUri() . URLUtil::encodePath($subPath);
287
-                list(, $displayPath) = URLUtil::splitPath($subPath);
288
-
289
-                $subNodes[$subPath]['subNode'] = $subNode;
290
-                $subNodes[$subPath]['fullPath'] = $fullPath;
291
-                $subNodes[$subPath]['displayPath'] = $displayPath;
292
-            }
293
-            uasort($subNodes, [$this, 'compareNodes']);
294
-
295
-            foreach ($subNodes as $subProps) {
296
-                $type = [
297
-                    'string' => 'Unknown',
298
-                    'icon'   => 'cog',
299
-                ];
300
-                if (isset($subProps['{DAV:}resourcetype'])) {
301
-                    $type = $this->mapResourceType($subProps['{DAV:}resourcetype']->getValue(), $subProps['subNode']);
302
-                }
303
-
304
-                $html .= '<tr>';
305
-                $html .= '<td class="nameColumn"><a href="' . $this->escapeHTML($subProps['fullPath']) . '"><span class="oi" data-glyph="' . $this->escapeHTML($type['icon']) . '"></span> ' . $this->escapeHTML($subProps['displayPath']) . '</a></td>';
306
-                $html .= '<td class="typeColumn">' . $this->escapeHTML($type['string']) . '</td>';
307
-                $html .= '<td>';
308
-                if (isset($subProps['{DAV:}getcontentlength'])) {
309
-                    $html .= $this->escapeHTML($subProps['{DAV:}getcontentlength'] . ' bytes');
310
-                }
311
-                $html .= '</td><td>';
312
-                if (isset($subProps['{DAV:}getlastmodified'])) {
313
-                    $lastMod = $subProps['{DAV:}getlastmodified']->getTime();
314
-                    $html .= $this->escapeHTML($lastMod->format('F j, Y, g:i a'));
315
-                }
316
-                $html .= '</td>';
317
-
318
-                $buttonActions = '';
319
-                if ($subProps['subNode'] instanceof DAV\IFile) {
320
-                    $buttonActions =  '<a href="' . $this->escapeHTML($subProps['fullPath']) . '?sabreAction=info"><span class="oi" data-glyph="info"></span></a>';
321
-                }
322
-                $this->server->emit('browserButtonActions', [$subProps['fullPath'], $subProps['subNode'], &$buttonActions]);
323
-
324
-                $html .= '<td>' . $buttonActions . '</td>';
325
-                $html .= '</tr>';
326
-            }
327
-
328
-            $html .= '</table>';
329
-
330
-        }
331
-
332
-        $html .= "</section>";
333
-        $html .= "<section><h1>Properties</h1>";
334
-        $html .= "<table class=\"propTable\">";
335
-
336
-        // Allprops request
337
-        $propFind = new PropFindAll($path);
338
-        $properties = $this->server->getPropertiesByNode($propFind, $node);
339
-
340
-        $properties = $propFind->getResultForMultiStatus()[200];
341
-
342
-        foreach ($properties as $propName => $propValue) {
343
-            if (!in_array($propName, $this->uninterestingProperties)) {
344
-                $html .= $this->drawPropertyRow($propName, $propValue);
345
-            }
346
-
347
-        }
348
-
349
-
350
-        $html .= "</table>";
351
-        $html .= "</section>";
352
-
353
-        /* Start of generating actions */
354
-
355
-        $output = '';
356
-        if ($this->enablePost) {
357
-            $this->server->emit('onHTMLActionsPanel', [$node, &$output]);
358
-        }
359
-
360
-        if ($output) {
361
-
362
-            $html .= "<section><h1>Actions</h1>";
363
-            $html .= "<div class=\"actions\">\n";
364
-            $html .= $output;
365
-            $html .= "</div>\n";
366
-            $html .= "</section>\n";
367
-        }
368
-
369
-        $html .= $this->generateFooter();
370
-
371
-        $this->server->httpResponse->setHeader('Content-Security-Policy', "img-src 'self'; style-src 'self';");
372
-
373
-        return $html;
374
-
375
-    }
376
-
377
-    /**
378
-     * Generates the 'plugins' page.
379
-     *
380
-     * @return string
381
-     */
382
-    public function generatePluginListing() {
383
-
384
-        $html = $this->generateHeader('Plugins');
385
-
386
-        $html .= "<section><h1>Plugins</h1>";
387
-        $html .= "<table class=\"propTable\">";
388
-        foreach ($this->server->getPlugins() as $plugin) {
389
-            $info = $plugin->getPluginInfo();
390
-            $html .= '<tr><th>' . $info['name'] . '</th>';
391
-            $html .= '<td>' . $info['description'] . '</td>';
392
-            $html .= '<td>';
393
-            if (isset($info['link']) && $info['link']) {
394
-                $html .= '<a href="' . $this->escapeHTML($info['link']) . '"><span class="oi" data-glyph="book"></span></a>';
395
-            }
396
-            $html .= '</td></tr>';
397
-        }
398
-        $html .= "</table>";
399
-        $html .= "</section>";
400
-
401
-        /* Start of generating actions */
402
-
403
-        $html .= $this->generateFooter();
404
-
405
-        return $html;
406
-
407
-    }
408
-
409
-    /**
410
-     * Generates the first block of HTML, including the <head> tag and page
411
-     * header.
412
-     *
413
-     * Returns footer.
414
-     *
415
-     * @param string $title
416
-     * @param string $path
417
-     * @return void
418
-     */
419
-    public function generateHeader($title, $path = null) {
26
+	/**
27
+	 * reference to server class
28
+	 *
29
+	 * @var Sabre\DAV\Server
30
+	 */
31
+	protected $server;
32
+
33
+	/**
34
+	 * enablePost turns on the 'actions' panel, which allows people to create
35
+	 * folders and upload files straight from a browser.
36
+	 *
37
+	 * @var bool
38
+	 */
39
+	protected $enablePost = true;
40
+
41
+	/**
42
+	 * A list of properties that are usually not interesting. This can cut down
43
+	 * the browser output a bit by removing the properties that most people
44
+	 * will likely not want to see.
45
+	 *
46
+	 * @var array
47
+	 */
48
+	public $uninterestingProperties = [
49
+		'{DAV:}supportedlock',
50
+		'{DAV:}acl-restrictions',
51
+		'{DAV:}supported-privilege-set',
52
+		'{DAV:}supported-method-set',
53
+	];
54
+
55
+	/**
56
+	 * Creates the object.
57
+	 *
58
+	 * By default it will allow file creation and uploads.
59
+	 * Specify the first argument as false to disable this
60
+	 *
61
+	 * @param bool $enablePost
62
+	 */
63
+	public function __construct($enablePost = true) {
64
+
65
+		$this->enablePost = $enablePost;
66
+
67
+	}
68
+
69
+	/**
70
+	 * Initializes the plugin and subscribes to events
71
+	 *
72
+	 * @param DAV\Server $server
73
+	 * @return void
74
+	 */
75
+	public function initialize(DAV\Server $server) {
76
+
77
+		$this->server = $server;
78
+		$this->server->on('method:GET', [$this, 'httpGetEarly'], 90);
79
+		$this->server->on('method:GET', [$this, 'httpGet'], 200);
80
+		$this->server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel'], 200);
81
+		if ($this->enablePost) $this->server->on('method:POST', [$this, 'httpPOST']);
82
+	}
83
+
84
+	/**
85
+	 * This method intercepts GET requests that have ?sabreAction=info
86
+	 * appended to the URL
87
+	 *
88
+	 * @param RequestInterface $request
89
+	 * @param ResponseInterface $response
90
+	 * @return bool
91
+	 */
92
+	public function httpGetEarly(RequestInterface $request, ResponseInterface $response) {
93
+
94
+		$params = $request->getQueryParameters();
95
+		if (isset($params['sabreAction']) && $params['sabreAction'] === 'info') {
96
+			return $this->httpGet($request, $response);
97
+		}
98
+
99
+	}
100
+
101
+	/**
102
+	 * This method intercepts GET requests to collections and returns the html
103
+	 *
104
+	 * @param RequestInterface $request
105
+	 * @param ResponseInterface $response
106
+	 * @return bool
107
+	 */
108
+	public function httpGet(RequestInterface $request, ResponseInterface $response) {
109
+
110
+		// We're not using straight-up $_GET, because we want everything to be
111
+		// unit testable.
112
+		$getVars = $request->getQueryParameters();
113
+
114
+		// CSP headers
115
+		$this->server->httpResponse->setHeader('Content-Security-Policy', "img-src 'self'; style-src 'self';");
116
+
117
+		$sabreAction = isset($getVars['sabreAction']) ? $getVars['sabreAction'] : null;
118
+
119
+		switch ($sabreAction) {
120
+
121
+			case 'asset' :
122
+				// Asset handling, such as images
123
+				$this->serveAsset(isset($getVars['assetName']) ? $getVars['assetName'] : null);
124
+				return false;
125
+			default :
126
+			case 'info' :
127
+				try {
128
+					$this->server->tree->getNodeForPath($request->getPath());
129
+				} catch (DAV\Exception\NotFound $e) {
130
+					// We're simply stopping when the file isn't found to not interfere
131
+					// with other plugins.
132
+					return;
133
+				}
134
+
135
+				$response->setStatus(200);
136
+				$response->setHeader('Content-Type', 'text/html; charset=utf-8');
137
+
138
+				$response->setBody(
139
+					$this->generateDirectoryIndex($request->getPath())
140
+				);
141
+
142
+				return false;
143
+
144
+			case 'plugins' :
145
+				$response->setStatus(200);
146
+				$response->setHeader('Content-Type', 'text/html; charset=utf-8');
147
+
148
+				$response->setBody(
149
+					$this->generatePluginListing()
150
+				);
151
+
152
+				return false;
153
+
154
+		}
155
+
156
+	}
157
+
158
+	/**
159
+	 * Handles POST requests for tree operations.
160
+	 *
161
+	 * @param RequestInterface $request
162
+	 * @param ResponseInterface $response
163
+	 * @return bool
164
+	 */
165
+	public function httpPOST(RequestInterface $request, ResponseInterface $response) {
166
+
167
+		$contentType = $request->getHeader('Content-Type');
168
+		list($contentType) = explode(';', $contentType);
169
+		if ($contentType !== 'application/x-www-form-urlencoded' &&
170
+			$contentType !== 'multipart/form-data') {
171
+				return;
172
+		}
173
+		$postVars = $request->getPostData();
174
+
175
+		if (!isset($postVars['sabreAction']))
176
+			return;
177
+
178
+		$uri = $request->getPath();
179
+
180
+		if ($this->server->emit('onBrowserPostAction', [$uri, $postVars['sabreAction'], $postVars])) {
181
+
182
+			switch ($postVars['sabreAction']) {
183
+
184
+				case 'mkcol' :
185
+					if (isset($postVars['name']) && trim($postVars['name'])) {
186
+						// Using basename() because we won't allow slashes
187
+						list(, $folderName) = URLUtil::splitPath(trim($postVars['name']));
188
+
189
+						if (isset($postVars['resourceType'])) {
190
+							$resourceType = explode(',', $postVars['resourceType']);
191
+						} else {
192
+							$resourceType = ['{DAV:}collection'];
193
+						}
194
+
195
+						$properties = [];
196
+						foreach ($postVars as $varName => $varValue) {
197
+							// Any _POST variable in clark notation is treated
198
+							// like a property.
199
+							if ($varName[0] === '{') {
200
+								// PHP will convert any dots to underscores.
201
+								// This leaves us with no way to differentiate
202
+								// the two.
203
+								// Therefore we replace the string *DOT* with a
204
+								// real dot. * is not allowed in uris so we
205
+								// should be good.
206
+								$varName = str_replace('*DOT*', '.', $varName);
207
+								$properties[$varName] = $varValue;
208
+							}
209
+						}
210
+
211
+						$mkCol = new MkCol(
212
+							$resourceType,
213
+							$properties
214
+						);
215
+						$this->server->createCollection($uri . '/' . $folderName, $mkCol);
216
+					}
217
+					break;
218
+
219
+				// @codeCoverageIgnoreStart
220
+				case 'put' :
221
+
222
+					if ($_FILES) $file = current($_FILES);
223
+					else break;
224
+
225
+					list(, $newName) = URLUtil::splitPath(trim($file['name']));
226
+					if (isset($postVars['name']) && trim($postVars['name']))
227
+						$newName = trim($postVars['name']);
228
+
229
+					// Making sure we only have a 'basename' component
230
+					list(, $newName) = URLUtil::splitPath($newName);
231
+
232
+					if (is_uploaded_file($file['tmp_name'])) {
233
+						$this->server->createFile($uri . '/' . $newName, fopen($file['tmp_name'], 'r'));
234
+					}
235
+					break;
236
+				// @codeCoverageIgnoreEnd
237
+
238
+			}
239
+
240
+		}
241
+		$response->setHeader('Location', $request->getUrl());
242
+		$response->setStatus(302);
243
+		return false;
244
+
245
+	}
246
+
247
+	/**
248
+	 * Escapes a string for html.
249
+	 *
250
+	 * @param string $value
251
+	 * @return string
252
+	 */
253
+	public function escapeHTML($value) {
254
+
255
+		return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
256
+
257
+	}
258
+
259
+	/**
260
+	 * Generates the html directory index for a given url
261
+	 *
262
+	 * @param string $path
263
+	 * @return string
264
+	 */
265
+	public function generateDirectoryIndex($path) {
266
+
267
+		$html = $this->generateHeader($path ? $path : '/', $path);
268
+
269
+		$node = $this->server->tree->getNodeForPath($path);
270
+		if ($node instanceof DAV\ICollection) {
271
+
272
+			$html .= "<section><h1>Nodes</h1>\n";
273
+			$html .= "<table class=\"nodeTable\">";
274
+
275
+			$subNodes = $this->server->getPropertiesForChildren($path, [
276
+				'{DAV:}displayname',
277
+				'{DAV:}resourcetype',
278
+				'{DAV:}getcontenttype',
279
+				'{DAV:}getcontentlength',
280
+				'{DAV:}getlastmodified',
281
+			]);
282
+
283
+			foreach ($subNodes as $subPath => $subProps) {
284
+
285
+				$subNode = $this->server->tree->getNodeForPath($subPath);
286
+				$fullPath = $this->server->getBaseUri() . URLUtil::encodePath($subPath);
287
+				list(, $displayPath) = URLUtil::splitPath($subPath);
288
+
289
+				$subNodes[$subPath]['subNode'] = $subNode;
290
+				$subNodes[$subPath]['fullPath'] = $fullPath;
291
+				$subNodes[$subPath]['displayPath'] = $displayPath;
292
+			}
293
+			uasort($subNodes, [$this, 'compareNodes']);
294
+
295
+			foreach ($subNodes as $subProps) {
296
+				$type = [
297
+					'string' => 'Unknown',
298
+					'icon'   => 'cog',
299
+				];
300
+				if (isset($subProps['{DAV:}resourcetype'])) {
301
+					$type = $this->mapResourceType($subProps['{DAV:}resourcetype']->getValue(), $subProps['subNode']);
302
+				}
303
+
304
+				$html .= '<tr>';
305
+				$html .= '<td class="nameColumn"><a href="' . $this->escapeHTML($subProps['fullPath']) . '"><span class="oi" data-glyph="' . $this->escapeHTML($type['icon']) . '"></span> ' . $this->escapeHTML($subProps['displayPath']) . '</a></td>';
306
+				$html .= '<td class="typeColumn">' . $this->escapeHTML($type['string']) . '</td>';
307
+				$html .= '<td>';
308
+				if (isset($subProps['{DAV:}getcontentlength'])) {
309
+					$html .= $this->escapeHTML($subProps['{DAV:}getcontentlength'] . ' bytes');
310
+				}
311
+				$html .= '</td><td>';
312
+				if (isset($subProps['{DAV:}getlastmodified'])) {
313
+					$lastMod = $subProps['{DAV:}getlastmodified']->getTime();
314
+					$html .= $this->escapeHTML($lastMod->format('F j, Y, g:i a'));
315
+				}
316
+				$html .= '</td>';
317
+
318
+				$buttonActions = '';
319
+				if ($subProps['subNode'] instanceof DAV\IFile) {
320
+					$buttonActions =  '<a href="' . $this->escapeHTML($subProps['fullPath']) . '?sabreAction=info"><span class="oi" data-glyph="info"></span></a>';
321
+				}
322
+				$this->server->emit('browserButtonActions', [$subProps['fullPath'], $subProps['subNode'], &$buttonActions]);
323
+
324
+				$html .= '<td>' . $buttonActions . '</td>';
325
+				$html .= '</tr>';
326
+			}
327
+
328
+			$html .= '</table>';
329
+
330
+		}
331
+
332
+		$html .= "</section>";
333
+		$html .= "<section><h1>Properties</h1>";
334
+		$html .= "<table class=\"propTable\">";
335
+
336
+		// Allprops request
337
+		$propFind = new PropFindAll($path);
338
+		$properties = $this->server->getPropertiesByNode($propFind, $node);
339
+
340
+		$properties = $propFind->getResultForMultiStatus()[200];
341
+
342
+		foreach ($properties as $propName => $propValue) {
343
+			if (!in_array($propName, $this->uninterestingProperties)) {
344
+				$html .= $this->drawPropertyRow($propName, $propValue);
345
+			}
346
+
347
+		}
348
+
349
+
350
+		$html .= "</table>";
351
+		$html .= "</section>";
352
+
353
+		/* Start of generating actions */
354
+
355
+		$output = '';
356
+		if ($this->enablePost) {
357
+			$this->server->emit('onHTMLActionsPanel', [$node, &$output]);
358
+		}
359
+
360
+		if ($output) {
361
+
362
+			$html .= "<section><h1>Actions</h1>";
363
+			$html .= "<div class=\"actions\">\n";
364
+			$html .= $output;
365
+			$html .= "</div>\n";
366
+			$html .= "</section>\n";
367
+		}
368
+
369
+		$html .= $this->generateFooter();
370
+
371
+		$this->server->httpResponse->setHeader('Content-Security-Policy', "img-src 'self'; style-src 'self';");
372
+
373
+		return $html;
374
+
375
+	}
376
+
377
+	/**
378
+	 * Generates the 'plugins' page.
379
+	 *
380
+	 * @return string
381
+	 */
382
+	public function generatePluginListing() {
383
+
384
+		$html = $this->generateHeader('Plugins');
385
+
386
+		$html .= "<section><h1>Plugins</h1>";
387
+		$html .= "<table class=\"propTable\">";
388
+		foreach ($this->server->getPlugins() as $plugin) {
389
+			$info = $plugin->getPluginInfo();
390
+			$html .= '<tr><th>' . $info['name'] . '</th>';
391
+			$html .= '<td>' . $info['description'] . '</td>';
392
+			$html .= '<td>';
393
+			if (isset($info['link']) && $info['link']) {
394
+				$html .= '<a href="' . $this->escapeHTML($info['link']) . '"><span class="oi" data-glyph="book"></span></a>';
395
+			}
396
+			$html .= '</td></tr>';
397
+		}
398
+		$html .= "</table>";
399
+		$html .= "</section>";
400
+
401
+		/* Start of generating actions */
402
+
403
+		$html .= $this->generateFooter();
404
+
405
+		return $html;
406
+
407
+	}
408
+
409
+	/**
410
+	 * Generates the first block of HTML, including the <head> tag and page
411
+	 * header.
412
+	 *
413
+	 * Returns footer.
414
+	 *
415
+	 * @param string $title
416
+	 * @param string $path
417
+	 * @return void
418
+	 */
419
+	public function generateHeader($title, $path = null) {
420 420
 
421
-        $version = DAV\Version::VERSION;
421
+		$version = DAV\Version::VERSION;
422 422
 
423
-        $vars = [
424
-            'title'     => $this->escapeHTML($title),
425
-            'favicon'   => $this->escapeHTML($this->getAssetUrl('favicon.ico')),
426
-            'style'     => $this->escapeHTML($this->getAssetUrl('sabredav.css')),
427
-            'iconstyle' => $this->escapeHTML($this->getAssetUrl('openiconic/open-iconic.css')),
428
-            'logo'      => $this->escapeHTML($this->getAssetUrl('sabredav.png')),
429
-            'baseUrl'   => $this->server->getBaseUri(),
430
-        ];
423
+		$vars = [
424
+			'title'     => $this->escapeHTML($title),
425
+			'favicon'   => $this->escapeHTML($this->getAssetUrl('favicon.ico')),
426
+			'style'     => $this->escapeHTML($this->getAssetUrl('sabredav.css')),
427
+			'iconstyle' => $this->escapeHTML($this->getAssetUrl('openiconic/open-iconic.css')),
428
+			'logo'      => $this->escapeHTML($this->getAssetUrl('sabredav.png')),
429
+			'baseUrl'   => $this->server->getBaseUri(),
430
+		];
431 431
 
432
-        $html = <<<HTML
432
+		$html = <<<HTML
433 433
 <!DOCTYPE html>
434 434
 <html>
435 435
 <head>
@@ -449,64 +449,64 @@  discard block
 block discarded – undo
449 449
     <nav>
450 450
 HTML;
451 451
 
452
-        // If the path is empty, there's no parent.
453
-        if ($path)  {
454
-            list($parentUri) = URLUtil::splitPath($path);
455
-            $fullPath = $this->server->getBaseUri() . URLUtil::encodePath($parentUri);
456
-            $html .= '<a href="' . $fullPath . '" class="btn">⇤ Go to parent</a>';
457
-        } else {
458
-            $html .= '<span class="btn disabled">⇤ Go to parent</span>';
459
-        }
452
+		// If the path is empty, there's no parent.
453
+		if ($path)  {
454
+			list($parentUri) = URLUtil::splitPath($path);
455
+			$fullPath = $this->server->getBaseUri() . URLUtil::encodePath($parentUri);
456
+			$html .= '<a href="' . $fullPath . '" class="btn">⇤ Go to parent</a>';
457
+		} else {
458
+			$html .= '<span class="btn disabled">⇤ Go to parent</span>';
459
+		}
460 460
 
461
-        $html .= ' <a href="?sabreAction=plugins" class="btn"><span class="oi" data-glyph="puzzle-piece"></span> Plugins</a>';
461
+		$html .= ' <a href="?sabreAction=plugins" class="btn"><span class="oi" data-glyph="puzzle-piece"></span> Plugins</a>';
462 462
 
463
-        $html .= "</nav>";
463
+		$html .= "</nav>";
464 464
 
465
-        return $html;
465
+		return $html;
466 466
 
467
-    }
467
+	}
468 468
 
469
-    /**
470
-     * Generates the page footer.
471
-     *
472
-     * Returns html.
473
-     *
474
-     * @return string
475
-     */
476
-    public function generateFooter() {
469
+	/**
470
+	 * Generates the page footer.
471
+	 *
472
+	 * Returns html.
473
+	 *
474
+	 * @return string
475
+	 */
476
+	public function generateFooter() {
477 477
 
478
-        $version = DAV\Version::VERSION;
479
-        return <<<HTML
478
+		$version = DAV\Version::VERSION;
479
+		return <<<HTML
480 480
 <footer>Generated by SabreDAV $version (c)2007-2015 <a href="http://sabre.io/">http://sabre.io/</a></footer>
481 481
 </body>
482 482
 </html>
483 483
 HTML;
484 484
 
485
-    }
486
-
487
-    /**
488
-     * This method is used to generate the 'actions panel' output for
489
-     * collections.
490
-     *
491
-     * This specifically generates the interfaces for creating new files, and
492
-     * creating new directories.
493
-     *
494
-     * @param DAV\INode $node
495
-     * @param mixed $output
496
-     * @return void
497
-     */
498
-    public function htmlActionsPanel(DAV\INode $node, &$output) {
499
-
500
-        if (!$node instanceof DAV\ICollection)
501
-            return;
502
-
503
-        // We also know fairly certain that if an object is a non-extended
504
-        // SimpleCollection, we won't need to show the panel either.
505
-        if (get_class($node) === 'Sabre\\DAV\\SimpleCollection')
506
-            return;
507
-
508
-        ob_start();
509
-        echo '<form method="post" action="">
485
+	}
486
+
487
+	/**
488
+	 * This method is used to generate the 'actions panel' output for
489
+	 * collections.
490
+	 *
491
+	 * This specifically generates the interfaces for creating new files, and
492
+	 * creating new directories.
493
+	 *
494
+	 * @param DAV\INode $node
495
+	 * @param mixed $output
496
+	 * @return void
497
+	 */
498
+	public function htmlActionsPanel(DAV\INode $node, &$output) {
499
+
500
+		if (!$node instanceof DAV\ICollection)
501
+			return;
502
+
503
+		// We also know fairly certain that if an object is a non-extended
504
+		// SimpleCollection, we won't need to show the panel either.
505
+		if (get_class($node) === 'Sabre\\DAV\\SimpleCollection')
506
+			return;
507
+
508
+		ob_start();
509
+		echo '<form method="post" action="">
510 510
         <h3>Create new folder</h3>
511 511
         <input type="hidden" name="sabreAction" value="mkcol" />
512 512
         <label>Name:</label> <input type="text" name="name" /><br />
@@ -521,277 +521,277 @@  discard block
 block discarded – undo
521 521
         </form>
522 522
         ';
523 523
 
524
-        $output .= ob_get_clean();
525
-
526
-    }
527
-
528
-    /**
529
-     * This method takes a path/name of an asset and turns it into url
530
-     * suiteable for http access.
531
-     *
532
-     * @param string $assetName
533
-     * @return string
534
-     */
535
-    protected function getAssetUrl($assetName) {
536
-
537
-        return $this->server->getBaseUri() . '?sabreAction=asset&assetName=' . urlencode($assetName);
538
-
539
-    }
540
-
541
-    /**
542
-     * This method returns a local pathname to an asset.
543
-     *
544
-     * @param string $assetName
545
-     * @return string
546
-     * @throws DAV\Exception\NotFound
547
-     */
548
-    protected function getLocalAssetPath($assetName) {
549
-
550
-        $assetDir = __DIR__ . '/assets/';
551
-        $path = $assetDir . $assetName;
552
-
553
-        // Making sure people aren't trying to escape from the base path.
554
-        $path = str_replace('\\', '/', $path);
555
-        if (strpos($path, '/../') !== false || strrchr($path, '/') === '/..') {
556
-            throw new DAV\Exception\NotFound('Path does not exist, or escaping from the base path was detected');
557
-        }
558
-        if (strpos(realpath($path), realpath($assetDir)) === 0 && file_exists($path)) {
559
-            return $path;
560
-        }
561
-        throw new DAV\Exception\NotFound('Path does not exist, or escaping from the base path was detected');
562
-    }
563
-
564
-    /**
565
-     * This method reads an asset from disk and generates a full http response.
566
-     *
567
-     * @param string $assetName
568
-     * @return void
569
-     */
570
-    protected function serveAsset($assetName) {
571
-
572
-        $assetPath = $this->getLocalAssetPath($assetName);
573
-
574
-        // Rudimentary mime type detection
575
-        $mime = 'application/octet-stream';
576
-        $map = [
577
-            'ico'  => 'image/vnd.microsoft.icon',
578
-            'png'  => 'image/png',
579
-            'css'  => 'text/css',
580
-        ];
581
-
582
-        $ext = substr($assetName, strrpos($assetName, '.') + 1);
583
-        if (isset($map[$ext])) {
584
-            $mime = $map[$ext];
585
-        }
586
-
587
-        $this->server->httpResponse->setHeader('Content-Type', $mime);
588
-        $this->server->httpResponse->setHeader('Content-Length', filesize($assetPath));
589
-        $this->server->httpResponse->setHeader('Cache-Control', 'public, max-age=1209600');
590
-        $this->server->httpResponse->setStatus(200);
591
-        $this->server->httpResponse->setBody(fopen($assetPath, 'r'));
592
-
593
-    }
594
-
595
-    /**
596
-     * Sort helper function: compares two directory entries based on type and
597
-     * display name. Collections sort above other types.
598
-     *
599
-     * @param array $a
600
-     * @param array $b
601
-     * @return int
602
-     */
603
-    protected function compareNodes($a, $b) {
604
-
605
-        $typeA = (isset($a['{DAV:}resourcetype']))
606
-            ? (in_array('{DAV:}collection', $a['{DAV:}resourcetype']->getValue()))
607
-            : false;
608
-
609
-        $typeB = (isset($b['{DAV:}resourcetype']))
610
-            ? (in_array('{DAV:}collection', $b['{DAV:}resourcetype']->getValue()))
611
-            : false;
612
-
613
-        // If same type, sort alphabetically by filename:
614
-        if ($typeA === $typeB) {
615
-            return strnatcasecmp($a['displayPath'], $b['displayPath']);
616
-        }
617
-        return (($typeA < $typeB) ? 1 : -1);
618
-
619
-    }
620
-
621
-    /**
622
-     * Maps a resource type to a human-readable string and icon.
623
-     *
624
-     * @param array $resourceTypes
625
-     * @param INode $node
626
-     * @return array
627
-     */
628
-    private function mapResourceType(array $resourceTypes, $node) {
629
-
630
-        if (!$resourceTypes) {
631
-            if ($node instanceof DAV\IFile) {
632
-                return [
633
-                    'string' => 'File',
634
-                    'icon'   => 'file',
635
-                ];
636
-            } else {
637
-                return [
638
-                    'string' => 'Unknown',
639
-                    'icon'   => 'cog',
640
-                ];
641
-            }
642
-        }
643
-
644
-        $types = [
645
-            '{http://calendarserver.org/ns/}calendar-proxy-write' => [
646
-                'string' => 'Proxy-Write',
647
-                'icon'   => 'people',
648
-            ],
649
-            '{http://calendarserver.org/ns/}calendar-proxy-read' => [
650
-                'string' => 'Proxy-Read',
651
-                'icon'   => 'people',
652
-            ],
653
-            '{urn:ietf:params:xml:ns:caldav}schedule-outbox' => [
654
-                'string' => 'Outbox',
655
-                'icon'   => 'inbox',
656
-            ],
657
-            '{urn:ietf:params:xml:ns:caldav}schedule-inbox' => [
658
-                'string' => 'Inbox',
659
-                'icon'   => 'inbox',
660
-            ],
661
-            '{urn:ietf:params:xml:ns:caldav}calendar' => [
662
-                'string' => 'Calendar',
663
-                'icon'   => 'calendar',
664
-            ],
665
-            '{http://calendarserver.org/ns/}shared-owner' => [
666
-                'string' => 'Shared',
667
-                'icon'   => 'calendar',
668
-            ],
669
-            '{http://calendarserver.org/ns/}subscribed' => [
670
-                'string' => 'Subscription',
671
-                'icon'   => 'calendar',
672
-            ],
673
-            '{urn:ietf:params:xml:ns:carddav}directory' => [
674
-                'string' => 'Directory',
675
-                'icon'   => 'globe',
676
-            ],
677
-            '{urn:ietf:params:xml:ns:carddav}addressbook' => [
678
-                'string' => 'Address book',
679
-                'icon'   => 'book',
680
-            ],
681
-            '{DAV:}principal' => [
682
-                'string' => 'Principal',
683
-                'icon'   => 'person',
684
-            ],
685
-            '{DAV:}collection' => [
686
-                'string' => 'Collection',
687
-                'icon'   => 'folder',
688
-            ],
689
-        ];
690
-
691
-        $info = [
692
-            'string' => [],
693
-            'icon'   => 'cog',
694
-        ];
695
-        foreach ($resourceTypes as $k => $resourceType) {
696
-            if (isset($types[$resourceType])) {
697
-                $info['string'][] = $types[$resourceType]['string'];
698
-            } else {
699
-                $info['string'][] = $resourceType;
700
-            }
701
-        }
702
-        foreach ($types as $key => $resourceInfo) {
703
-            if (in_array($key, $resourceTypes)) {
704
-                $info['icon'] = $resourceInfo['icon'];
705
-                break;
706
-            }
707
-        }
708
-        $info['string'] = implode(', ', $info['string']);
709
-
710
-        return $info;
711
-
712
-    }
713
-
714
-    /**
715
-     * Draws a table row for a property
716
-     *
717
-     * @param string $name
718
-     * @param mixed $value
719
-     * @return string
720
-     */
721
-    private function drawPropertyRow($name, $value) {
722
-
723
-        $html = new HtmlOutputHelper(
724
-            $this->server->getBaseUri(),
725
-            $this->server->xml->namespaceMap
726
-        );
727
-
728
-        return "<tr><th>" . $html->xmlName($name) . "</th><td>" . $this->drawPropertyValue($html, $value) . "</td></tr>";
729
-
730
-    }
731
-
732
-    /**
733
-     * Draws a table row for a property
734
-     *
735
-     * @param HtmlOutputHelper $html
736
-     * @param mixed $value
737
-     * @return string
738
-     */
739
-    private function drawPropertyValue($html, $value) {
740
-
741
-        if (is_scalar($value)) {
742
-            return $html->h($value);
743
-        } elseif ($value instanceof HtmlOutput) {
744
-            return $value->toHtml($html);
745
-        } elseif ($value instanceof \Sabre\Xml\XmlSerializable) {
746
-
747
-            // There's no default html output for this property, we're going
748
-            // to output the actual xml serialization instead.
749
-            $xml = $this->server->xml->write('{DAV:}root', $value, $this->server->getBaseUri());
750
-            // removing first and last line, as they contain our root
751
-            // element.
752
-            $xml = explode("\n", $xml);
753
-            $xml = array_slice($xml, 2, -2);
754
-            return "<pre>" . $html->h(implode("\n", $xml)) . "</pre>";
755
-
756
-        } else {
757
-            return "<em>unknown</em>";
758
-        }
759
-
760
-    }
761
-
762
-    /**
763
-     * Returns a plugin name.
764
-     *
765
-     * Using this name other plugins will be able to access other plugins;
766
-     * using \Sabre\DAV\Server::getPlugin
767
-     *
768
-     * @return string
769
-     */
770
-    public function getPluginName() {
771
-
772
-        return 'browser';
773
-
774
-    }
775
-
776
-    /**
777
-     * Returns a bunch of meta-data about the plugin.
778
-     *
779
-     * Providing this information is optional, and is mainly displayed by the
780
-     * Browser plugin.
781
-     *
782
-     * The description key in the returned array may contain html and will not
783
-     * be sanitized.
784
-     *
785
-     * @return array
786
-     */
787
-    public function getPluginInfo() {
788
-
789
-        return [
790
-            'name'        => $this->getPluginName(),
791
-            'description' => 'Generates HTML indexes and debug information for your sabre/dav server',
792
-            'link'        => 'http://sabre.io/dav/browser-plugin/',
793
-        ];
794
-
795
-    }
524
+		$output .= ob_get_clean();
525
+
526
+	}
527
+
528
+	/**
529
+	 * This method takes a path/name of an asset and turns it into url
530
+	 * suiteable for http access.
531
+	 *
532
+	 * @param string $assetName
533
+	 * @return string
534
+	 */
535
+	protected function getAssetUrl($assetName) {
536
+
537
+		return $this->server->getBaseUri() . '?sabreAction=asset&assetName=' . urlencode($assetName);
538
+
539
+	}
540
+
541
+	/**
542
+	 * This method returns a local pathname to an asset.
543
+	 *
544
+	 * @param string $assetName
545
+	 * @return string
546
+	 * @throws DAV\Exception\NotFound
547
+	 */
548
+	protected function getLocalAssetPath($assetName) {
549
+
550
+		$assetDir = __DIR__ . '/assets/';
551
+		$path = $assetDir . $assetName;
552
+
553
+		// Making sure people aren't trying to escape from the base path.
554
+		$path = str_replace('\\', '/', $path);
555
+		if (strpos($path, '/../') !== false || strrchr($path, '/') === '/..') {
556
+			throw new DAV\Exception\NotFound('Path does not exist, or escaping from the base path was detected');
557
+		}
558
+		if (strpos(realpath($path), realpath($assetDir)) === 0 && file_exists($path)) {
559
+			return $path;
560
+		}
561
+		throw new DAV\Exception\NotFound('Path does not exist, or escaping from the base path was detected');
562
+	}
563
+
564
+	/**
565
+	 * This method reads an asset from disk and generates a full http response.
566
+	 *
567
+	 * @param string $assetName
568
+	 * @return void
569
+	 */
570
+	protected function serveAsset($assetName) {
571
+
572
+		$assetPath = $this->getLocalAssetPath($assetName);
573
+
574
+		// Rudimentary mime type detection
575
+		$mime = 'application/octet-stream';
576
+		$map = [
577
+			'ico'  => 'image/vnd.microsoft.icon',
578
+			'png'  => 'image/png',
579
+			'css'  => 'text/css',
580
+		];
581
+
582
+		$ext = substr($assetName, strrpos($assetName, '.') + 1);
583
+		if (isset($map[$ext])) {
584
+			$mime = $map[$ext];
585
+		}
586
+
587
+		$this->server->httpResponse->setHeader('Content-Type', $mime);
588
+		$this->server->httpResponse->setHeader('Content-Length', filesize($assetPath));
589
+		$this->server->httpResponse->setHeader('Cache-Control', 'public, max-age=1209600');
590
+		$this->server->httpResponse->setStatus(200);
591
+		$this->server->httpResponse->setBody(fopen($assetPath, 'r'));
592
+
593
+	}
594
+
595
+	/**
596
+	 * Sort helper function: compares two directory entries based on type and
597
+	 * display name. Collections sort above other types.
598
+	 *
599
+	 * @param array $a
600
+	 * @param array $b
601
+	 * @return int
602
+	 */
603
+	protected function compareNodes($a, $b) {
604
+
605
+		$typeA = (isset($a['{DAV:}resourcetype']))
606
+			? (in_array('{DAV:}collection', $a['{DAV:}resourcetype']->getValue()))
607
+			: false;
608
+
609
+		$typeB = (isset($b['{DAV:}resourcetype']))
610
+			? (in_array('{DAV:}collection', $b['{DAV:}resourcetype']->getValue()))
611
+			: false;
612
+
613
+		// If same type, sort alphabetically by filename:
614
+		if ($typeA === $typeB) {
615
+			return strnatcasecmp($a['displayPath'], $b['displayPath']);
616
+		}
617
+		return (($typeA < $typeB) ? 1 : -1);
618
+
619
+	}
620
+
621
+	/**
622
+	 * Maps a resource type to a human-readable string and icon.
623
+	 *
624
+	 * @param array $resourceTypes
625
+	 * @param INode $node
626
+	 * @return array
627
+	 */
628
+	private function mapResourceType(array $resourceTypes, $node) {
629
+
630
+		if (!$resourceTypes) {
631
+			if ($node instanceof DAV\IFile) {
632
+				return [
633
+					'string' => 'File',
634
+					'icon'   => 'file',
635
+				];
636
+			} else {
637
+				return [
638
+					'string' => 'Unknown',
639
+					'icon'   => 'cog',
640
+				];
641
+			}
642
+		}
643
+
644
+		$types = [
645
+			'{http://calendarserver.org/ns/}calendar-proxy-write' => [
646
+				'string' => 'Proxy-Write',
647
+				'icon'   => 'people',
648
+			],
649
+			'{http://calendarserver.org/ns/}calendar-proxy-read' => [
650
+				'string' => 'Proxy-Read',
651
+				'icon'   => 'people',
652
+			],
653
+			'{urn:ietf:params:xml:ns:caldav}schedule-outbox' => [
654
+				'string' => 'Outbox',
655
+				'icon'   => 'inbox',
656
+			],
657
+			'{urn:ietf:params:xml:ns:caldav}schedule-inbox' => [
658
+				'string' => 'Inbox',
659
+				'icon'   => 'inbox',
660
+			],
661
+			'{urn:ietf:params:xml:ns:caldav}calendar' => [
662
+				'string' => 'Calendar',
663
+				'icon'   => 'calendar',
664
+			],
665
+			'{http://calendarserver.org/ns/}shared-owner' => [
666
+				'string' => 'Shared',
667
+				'icon'   => 'calendar',
668
+			],
669
+			'{http://calendarserver.org/ns/}subscribed' => [
670
+				'string' => 'Subscription',
671
+				'icon'   => 'calendar',
672
+			],
673
+			'{urn:ietf:params:xml:ns:carddav}directory' => [
674
+				'string' => 'Directory',
675
+				'icon'   => 'globe',
676
+			],
677
+			'{urn:ietf:params:xml:ns:carddav}addressbook' => [
678
+				'string' => 'Address book',
679
+				'icon'   => 'book',
680
+			],
681
+			'{DAV:}principal' => [
682
+				'string' => 'Principal',
683
+				'icon'   => 'person',
684
+			],
685
+			'{DAV:}collection' => [
686
+				'string' => 'Collection',
687
+				'icon'   => 'folder',
688
+			],
689
+		];
690
+
691
+		$info = [
692
+			'string' => [],
693
+			'icon'   => 'cog',
694
+		];
695
+		foreach ($resourceTypes as $k => $resourceType) {
696
+			if (isset($types[$resourceType])) {
697
+				$info['string'][] = $types[$resourceType]['string'];
698
+			} else {
699
+				$info['string'][] = $resourceType;
700
+			}
701
+		}
702
+		foreach ($types as $key => $resourceInfo) {
703
+			if (in_array($key, $resourceTypes)) {
704
+				$info['icon'] = $resourceInfo['icon'];
705
+				break;
706
+			}
707
+		}
708
+		$info['string'] = implode(', ', $info['string']);
709
+
710
+		return $info;
711
+
712
+	}
713
+
714
+	/**
715
+	 * Draws a table row for a property
716
+	 *
717
+	 * @param string $name
718
+	 * @param mixed $value
719
+	 * @return string
720
+	 */
721
+	private function drawPropertyRow($name, $value) {
722
+
723
+		$html = new HtmlOutputHelper(
724
+			$this->server->getBaseUri(),
725
+			$this->server->xml->namespaceMap
726
+		);
727
+
728
+		return "<tr><th>" . $html->xmlName($name) . "</th><td>" . $this->drawPropertyValue($html, $value) . "</td></tr>";
729
+
730
+	}
731
+
732
+	/**
733
+	 * Draws a table row for a property
734
+	 *
735
+	 * @param HtmlOutputHelper $html
736
+	 * @param mixed $value
737
+	 * @return string
738
+	 */
739
+	private function drawPropertyValue($html, $value) {
740
+
741
+		if (is_scalar($value)) {
742
+			return $html->h($value);
743
+		} elseif ($value instanceof HtmlOutput) {
744
+			return $value->toHtml($html);
745
+		} elseif ($value instanceof \Sabre\Xml\XmlSerializable) {
746
+
747
+			// There's no default html output for this property, we're going
748
+			// to output the actual xml serialization instead.
749
+			$xml = $this->server->xml->write('{DAV:}root', $value, $this->server->getBaseUri());
750
+			// removing first and last line, as they contain our root
751
+			// element.
752
+			$xml = explode("\n", $xml);
753
+			$xml = array_slice($xml, 2, -2);
754
+			return "<pre>" . $html->h(implode("\n", $xml)) . "</pre>";
755
+
756
+		} else {
757
+			return "<em>unknown</em>";
758
+		}
759
+
760
+	}
761
+
762
+	/**
763
+	 * Returns a plugin name.
764
+	 *
765
+	 * Using this name other plugins will be able to access other plugins;
766
+	 * using \Sabre\DAV\Server::getPlugin
767
+	 *
768
+	 * @return string
769
+	 */
770
+	public function getPluginName() {
771
+
772
+		return 'browser';
773
+
774
+	}
775
+
776
+	/**
777
+	 * Returns a bunch of meta-data about the plugin.
778
+	 *
779
+	 * Providing this information is optional, and is mainly displayed by the
780
+	 * Browser plugin.
781
+	 *
782
+	 * The description key in the returned array may contain html and will not
783
+	 * be sanitized.
784
+	 *
785
+	 * @return array
786
+	 */
787
+	public function getPluginInfo() {
788
+
789
+		return [
790
+			'name'        => $this->getPluginName(),
791
+			'description' => 'Generates HTML indexes and debug information for your sabre/dav server',
792
+			'link'        => 'http://sabre.io/dav/browser-plugin/',
793
+		];
794
+
795
+	}
796 796
 
797 797
 }
Please login to merge, or discard this patch.
Braces   +20 added lines, -11 removed lines patch added patch discarded remove patch
@@ -78,7 +78,9 @@  discard block
 block discarded – undo
78 78
         $this->server->on('method:GET', [$this, 'httpGetEarly'], 90);
79 79
         $this->server->on('method:GET', [$this, 'httpGet'], 200);
80 80
         $this->server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel'], 200);
81
-        if ($this->enablePost) $this->server->on('method:POST', [$this, 'httpPOST']);
81
+        if ($this->enablePost) {
82
+        	$this->server->on('method:POST', [$this, 'httpPOST']);
83
+        }
82 84
     }
83 85
 
84 86
     /**
@@ -172,8 +174,9 @@  discard block
 block discarded – undo
172 174
         }
173 175
         $postVars = $request->getPostData();
174 176
 
175
-        if (!isset($postVars['sabreAction']))
176
-            return;
177
+        if (!isset($postVars['sabreAction'])) {
178
+                    return;
179
+        }
177 180
 
178 181
         $uri = $request->getPath();
179 182
 
@@ -219,12 +222,16 @@  discard block
 block discarded – undo
219 222
                 // @codeCoverageIgnoreStart
220 223
                 case 'put' :
221 224
 
222
-                    if ($_FILES) $file = current($_FILES);
223
-                    else break;
225
+                    if ($_FILES) {
226
+                    	$file = current($_FILES);
227
+                    } else {
228
+                    	break;
229
+                    }
224 230
 
225 231
                     list(, $newName) = URLUtil::splitPath(trim($file['name']));
226
-                    if (isset($postVars['name']) && trim($postVars['name']))
227
-                        $newName = trim($postVars['name']);
232
+                    if (isset($postVars['name']) && trim($postVars['name'])) {
233
+                                            $newName = trim($postVars['name']);
234
+                    }
228 235
 
229 236
                     // Making sure we only have a 'basename' component
230 237
                     list(, $newName) = URLUtil::splitPath($newName);
@@ -497,13 +504,15 @@  discard block
 block discarded – undo
497 504
      */
498 505
     public function htmlActionsPanel(DAV\INode $node, &$output) {
499 506
 
500
-        if (!$node instanceof DAV\ICollection)
501
-            return;
507
+        if (!$node instanceof DAV\ICollection) {
508
+                    return;
509
+        }
502 510
 
503 511
         // We also know fairly certain that if an object is a non-extended
504 512
         // SimpleCollection, we won't need to show the panel either.
505
-        if (get_class($node) === 'Sabre\\DAV\\SimpleCollection')
506
-            return;
513
+        if (get_class($node) === 'Sabre\\DAV\\SimpleCollection') {
514
+                    return;
515
+        }
507 516
 
508 517
         ob_start();
509 518
         echo '<form method="post" action="">
Please login to merge, or discard this patch.
libraries/SabreDAV/DAV/Xml/Request/PropFind.php 1 patch
Indentation   +60 added lines, -60 removed lines patch added patch discarded remove patch
@@ -19,65 +19,65 @@
 block discarded – undo
19 19
  */
20 20
 class PropFind implements XmlDeserializable {
21 21
 
22
-    /**
23
-     * If this is set to true, this was an 'allprop' request.
24
-     *
25
-     * @var bool
26
-     */
27
-    public $allProp = false;
28
-
29
-    /**
30
-     * The property list
31
-     *
32
-     * @var null|array
33
-     */
34
-    public $properties;
35
-
36
-    /**
37
-     * The deserialize method is called during xml parsing.
38
-     *
39
-     * This method is called statictly, this is because in theory this method
40
-     * may be used as a type of constructor, or factory method.
41
-     *
42
-     * Often you want to return an instance of the current class, but you are
43
-     * free to return other data as well.
44
-     *
45
-     * You are responsible for advancing the reader to the next element. Not
46
-     * doing anything will result in a never-ending loop.
47
-     *
48
-     * If you just want to skip parsing for this element altogether, you can
49
-     * just call $reader->next();
50
-     *
51
-     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
52
-     * the next element.
53
-     *
54
-     * @param Reader $reader
55
-     * @return mixed
56
-     */
57
-    static function xmlDeserialize(Reader $reader) {
58
-
59
-        $self = new self();
60
-
61
-        $reader->pushContext();
62
-        $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Element\Elements';
63
-
64
-        foreach (KeyValue::xmlDeserialize($reader) as $k => $v) {
65
-
66
-            switch ($k) {
67
-                case '{DAV:}prop' :
68
-                    $self->properties = $v;
69
-                    break;
70
-                case '{DAV:}allprop' :
71
-                    $self->allProp = true;
72
-
73
-            }
74
-
75
-        }
76
-
77
-        $reader->popContext();
78
-
79
-        return $self;
80
-
81
-    }
22
+	/**
23
+	 * If this is set to true, this was an 'allprop' request.
24
+	 *
25
+	 * @var bool
26
+	 */
27
+	public $allProp = false;
28
+
29
+	/**
30
+	 * The property list
31
+	 *
32
+	 * @var null|array
33
+	 */
34
+	public $properties;
35
+
36
+	/**
37
+	 * The deserialize method is called during xml parsing.
38
+	 *
39
+	 * This method is called statictly, this is because in theory this method
40
+	 * may be used as a type of constructor, or factory method.
41
+	 *
42
+	 * Often you want to return an instance of the current class, but you are
43
+	 * free to return other data as well.
44
+	 *
45
+	 * You are responsible for advancing the reader to the next element. Not
46
+	 * doing anything will result in a never-ending loop.
47
+	 *
48
+	 * If you just want to skip parsing for this element altogether, you can
49
+	 * just call $reader->next();
50
+	 *
51
+	 * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
52
+	 * the next element.
53
+	 *
54
+	 * @param Reader $reader
55
+	 * @return mixed
56
+	 */
57
+	static function xmlDeserialize(Reader $reader) {
58
+
59
+		$self = new self();
60
+
61
+		$reader->pushContext();
62
+		$reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Element\Elements';
63
+
64
+		foreach (KeyValue::xmlDeserialize($reader) as $k => $v) {
65
+
66
+			switch ($k) {
67
+				case '{DAV:}prop' :
68
+					$self->properties = $v;
69
+					break;
70
+				case '{DAV:}allprop' :
71
+					$self->allProp = true;
72
+
73
+			}
74
+
75
+		}
76
+
77
+		$reader->popContext();
78
+
79
+		return $self;
80
+
81
+	}
82 82
 
83 83
 }
Please login to merge, or discard this patch.