Completed
Push — developer ( d751a3...e1a3df )
by Błażej
164:44 queued 111:37
created
libraries/SabreDAV/DAV/PropPatch.php 1 patch
Indentation   +350 added lines, -350 removed lines patch added patch discarded remove patch
@@ -19,355 +19,355 @@
 block discarded – undo
19 19
  */
20 20
 class PropPatch {
21 21
 
22
-    /**
23
-     * Properties that are being updated.
24
-     *
25
-     * This is a key-value list. If the value is null, the property is supposed
26
-     * to be deleted.
27
-     *
28
-     * @var array
29
-     */
30
-    protected $mutations;
31
-
32
-    /**
33
-     * A list of properties and the result of the update. The result is in the
34
-     * form of a HTTP status code.
35
-     *
36
-     * @var array
37
-     */
38
-    protected $result = [];
39
-
40
-    /**
41
-     * This is the list of callbacks when we're performing the actual update.
42
-     *
43
-     * @var array
44
-     */
45
-    protected $propertyUpdateCallbacks = [];
46
-
47
-    /**
48
-     * This property will be set to true if the operation failed.
49
-     *
50
-     * @var bool
51
-     */
52
-    protected $failed = false;
53
-
54
-    /**
55
-     * Constructor
56
-     *
57
-     * @param array $mutations A list of updates
58
-     */
59
-    public function __construct(array $mutations) {
60
-
61
-        $this->mutations = $mutations;
62
-
63
-    }
64
-
65
-    /**
66
-     * Call this function if you wish to handle updating certain properties.
67
-     * For instance, your class may be responsible for handling updates for the
68
-     * {DAV:}displayname property.
69
-     *
70
-     * In that case, call this method with the first argument
71
-     * "{DAV:}displayname" and a second argument that's a method that does the
72
-     * actual updating.
73
-     *
74
-     * It's possible to specify more than one property as an array.
75
-     *
76
-     * The callback must return a boolean or an it. If the result is true, the
77
-     * operation was considered successful. If it's false, it's consided
78
-     * failed.
79
-     *
80
-     * If the result is an integer, we'll use that integer as the http status
81
-     * code associated with the operation.
82
-     *
83
-     * @param string|string[] $properties
84
-     * @param callable $callback
85
-     * @return void
86
-     */
87
-    public function handle($properties, callable $callback) {
88
-
89
-        $usedProperties = [];
90
-        foreach ((array)$properties as $propertyName) {
91
-
92
-            if (array_key_exists($propertyName, $this->mutations) && !isset($this->result[$propertyName])) {
93
-
94
-                $usedProperties[] = $propertyName;
95
-                // HTTP Accepted
96
-                $this->result[$propertyName] = 202;
97
-            }
98
-
99
-        }
100
-
101
-        // Only registering if there's any unhandled properties.
102
-        if (!$usedProperties) {
103
-            return;
104
-        }
105
-        $this->propertyUpdateCallbacks[] = [
106
-            // If the original argument to this method was a string, we need
107
-            // to also make sure that it stays that way, so the commit function
108
-            // knows how to format the arguments to the callback.
109
-            is_string($properties) ? $properties : $usedProperties,
110
-            $callback
111
-        ];
112
-
113
-    }
114
-
115
-    /**
116
-     * Call this function if you wish to handle _all_ properties that haven't
117
-     * been handled by anything else yet. Note that you effectively claim with
118
-     * this that you promise to process _all_ properties that are coming in.
119
-     *
120
-     * @param callable $callback
121
-     * @return void
122
-     */
123
-    public function handleRemaining(callable $callback) {
124
-
125
-        $properties = $this->getRemainingMutations();
126
-        if (!$properties) {
127
-            // Nothing to do, don't register callback
128
-            return;
129
-        }
130
-
131
-        foreach ($properties as $propertyName) {
132
-            // HTTP Accepted
133
-            $this->result[$propertyName] = 202;
134
-
135
-            $this->propertyUpdateCallbacks[] = [
136
-                $properties,
137
-                $callback
138
-            ];
139
-        }
140
-
141
-    }
142
-
143
-    /**
144
-     * Sets the result code for one or more properties.
145
-     *
146
-     * @param string|string[] $properties
147
-     * @param int $resultCode
148
-     * @return void
149
-     */
150
-    public function setResultCode($properties, $resultCode) {
151
-
152
-        foreach ((array)$properties as $propertyName) {
153
-            $this->result[$propertyName] = $resultCode;
154
-        }
155
-
156
-        if ($resultCode >= 400) {
157
-            $this->failed = true;
158
-        }
159
-
160
-    }
161
-
162
-    /**
163
-     * Sets the result code for all properties that did not have a result yet.
164
-     *
165
-     * @param int $resultCode
166
-     * @return void
167
-     */
168
-    public function setRemainingResultCode($resultCode) {
169
-
170
-        $this->setResultCode(
171
-            $this->getRemainingMutations(),
172
-            $resultCode
173
-        );
174
-
175
-    }
176
-
177
-    /**
178
-     * Returns the list of properties that don't have a result code yet.
179
-     *
180
-     * This method returns a list of property names, but not its values.
181
-     *
182
-     * @return string[]
183
-     */
184
-    public function getRemainingMutations() {
185
-
186
-        $remaining = [];
187
-        foreach ($this->mutations as $propertyName => $propValue) {
188
-            if (!isset($this->result[$propertyName])) {
189
-                $remaining[] = $propertyName;
190
-            }
191
-        }
192
-
193
-        return $remaining;
194
-
195
-    }
196
-
197
-    /**
198
-     * Returns the list of properties that don't have a result code yet.
199
-     *
200
-     * This method returns list of properties and their values.
201
-     *
202
-     * @return array
203
-     */
204
-    public function getRemainingValues() {
205
-
206
-        $remaining = [];
207
-        foreach ($this->mutations as $propertyName => $propValue) {
208
-            if (!isset($this->result[$propertyName])) {
209
-                $remaining[$propertyName] = $propValue;
210
-            }
211
-        }
212
-
213
-        return $remaining;
214
-
215
-    }
216
-
217
-    /**
218
-     * Performs the actual update, and calls all callbacks.
219
-     *
220
-     * This method returns true or false depending on if the operation was
221
-     * successful.
222
-     *
223
-     * @return bool
224
-     */
225
-    public function commit() {
226
-
227
-        // First we validate if every property has a handler
228
-        foreach ($this->mutations as $propertyName => $value) {
229
-
230
-            if (!isset($this->result[$propertyName])) {
231
-                $this->failed = true;
232
-                $this->result[$propertyName] = 403;
233
-            }
234
-
235
-        }
236
-
237
-        foreach ($this->propertyUpdateCallbacks as $callbackInfo) {
238
-
239
-            if ($this->failed) {
240
-                break;
241
-            }
242
-            if (is_string($callbackInfo[0])) {
243
-                $this->doCallbackSingleProp($callbackInfo[0], $callbackInfo[1]);
244
-            } else {
245
-                $this->doCallbackMultiProp($callbackInfo[0], $callbackInfo[1]);
246
-            }
247
-
248
-        }
249
-
250
-        /**
251
-         * If anywhere in this operation updating a property failed, we must
252
-         * update all other properties accordingly.
253
-         */
254
-        if ($this->failed) {
255
-
256
-            foreach ($this->result as $propertyName => $status) {
257
-                if ($status === 202) {
258
-                    // Failed dependency
259
-                    $this->result[$propertyName] = 424;
260
-                }
261
-            }
262
-
263
-        }
264
-
265
-        return !$this->failed;
266
-
267
-    }
268
-
269
-    /**
270
-     * Executes a property callback with the single-property syntax.
271
-     *
272
-     * @param string $propertyName
273
-     * @param callable $callback
274
-     * @return void
275
-     */
276
-    private function doCallBackSingleProp($propertyName, callable $callback) {
277
-
278
-        $result = $callback($this->mutations[$propertyName]);
279
-        if (is_bool($result)) {
280
-            if ($result) {
281
-                if (is_null($this->mutations[$propertyName])) {
282
-                    // Delete
283
-                    $result = 204;
284
-                } else {
285
-                    // Update
286
-                    $result = 200;
287
-                }
288
-            } else {
289
-                // Fail
290
-                $result = 403;
291
-            }
292
-        }
293
-        if (!is_int($result)) {
294
-            throw new UnexpectedValueException('A callback sent to handle() did not return an int or a bool');
295
-        }
296
-        $this->result[$propertyName] = $result;
297
-        if ($result >= 400) {
298
-            $this->failed = true;
299
-        }
300
-
301
-    }
302
-
303
-    /**
304
-     * Executes a property callback with the multi-property syntax.
305
-     *
306
-     * @param array $propertyList
307
-     * @param callable $callback
308
-     * @return void
309
-     */
310
-    private function doCallBackMultiProp(array $propertyList, callable $callback) {
311
-
312
-        $argument = [];
313
-        foreach ($propertyList as $propertyName) {
314
-            $argument[$propertyName] = $this->mutations[$propertyName];
315
-        }
316
-
317
-        $result = $callback($argument);
318
-
319
-        if (is_array($result)) {
320
-            foreach ($propertyList as $propertyName) {
321
-                if (!isset($result[$propertyName])) {
322
-                    $resultCode = 500;
323
-                } else {
324
-                    $resultCode = $result[$propertyName];
325
-                }
326
-                if ($resultCode >= 400) {
327
-                    $this->failed = true;
328
-                }
329
-                $this->result[$propertyName] = $resultCode;
330
-
331
-            }
332
-        } elseif ($result === true) {
333
-
334
-            // Success
335
-            foreach ($argument as $propertyName => $propertyValue) {
336
-                $this->result[$propertyName] = is_null($propertyValue) ? 204 : 200;
337
-            }
338
-
339
-        } elseif ($result === false) {
340
-            // Fail :(
341
-            $this->failed = true;
342
-            foreach ($propertyList as $propertyName) {
343
-                $this->result[$propertyName] = 403;
344
-            }
345
-        } else {
346
-            throw new UnexpectedValueException('A callback sent to handle() did not return an array or a bool');
347
-        }
348
-
349
-    }
350
-
351
-    /**
352
-     * Returns the result of the operation.
353
-     *
354
-     * @return array
355
-     */
356
-    public function getResult() {
357
-
358
-        return $this->result;
359
-
360
-    }
361
-
362
-    /**
363
-     * Returns the full list of mutations
364
-     *
365
-     * @return array
366
-     */
367
-    public function getMutations() {
368
-
369
-        return $this->mutations;
370
-
371
-    }
22
+	/**
23
+	 * Properties that are being updated.
24
+	 *
25
+	 * This is a key-value list. If the value is null, the property is supposed
26
+	 * to be deleted.
27
+	 *
28
+	 * @var array
29
+	 */
30
+	protected $mutations;
31
+
32
+	/**
33
+	 * A list of properties and the result of the update. The result is in the
34
+	 * form of a HTTP status code.
35
+	 *
36
+	 * @var array
37
+	 */
38
+	protected $result = [];
39
+
40
+	/**
41
+	 * This is the list of callbacks when we're performing the actual update.
42
+	 *
43
+	 * @var array
44
+	 */
45
+	protected $propertyUpdateCallbacks = [];
46
+
47
+	/**
48
+	 * This property will be set to true if the operation failed.
49
+	 *
50
+	 * @var bool
51
+	 */
52
+	protected $failed = false;
53
+
54
+	/**
55
+	 * Constructor
56
+	 *
57
+	 * @param array $mutations A list of updates
58
+	 */
59
+	public function __construct(array $mutations) {
60
+
61
+		$this->mutations = $mutations;
62
+
63
+	}
64
+
65
+	/**
66
+	 * Call this function if you wish to handle updating certain properties.
67
+	 * For instance, your class may be responsible for handling updates for the
68
+	 * {DAV:}displayname property.
69
+	 *
70
+	 * In that case, call this method with the first argument
71
+	 * "{DAV:}displayname" and a second argument that's a method that does the
72
+	 * actual updating.
73
+	 *
74
+	 * It's possible to specify more than one property as an array.
75
+	 *
76
+	 * The callback must return a boolean or an it. If the result is true, the
77
+	 * operation was considered successful. If it's false, it's consided
78
+	 * failed.
79
+	 *
80
+	 * If the result is an integer, we'll use that integer as the http status
81
+	 * code associated with the operation.
82
+	 *
83
+	 * @param string|string[] $properties
84
+	 * @param callable $callback
85
+	 * @return void
86
+	 */
87
+	public function handle($properties, callable $callback) {
88
+
89
+		$usedProperties = [];
90
+		foreach ((array)$properties as $propertyName) {
91
+
92
+			if (array_key_exists($propertyName, $this->mutations) && !isset($this->result[$propertyName])) {
93
+
94
+				$usedProperties[] = $propertyName;
95
+				// HTTP Accepted
96
+				$this->result[$propertyName] = 202;
97
+			}
98
+
99
+		}
100
+
101
+		// Only registering if there's any unhandled properties.
102
+		if (!$usedProperties) {
103
+			return;
104
+		}
105
+		$this->propertyUpdateCallbacks[] = [
106
+			// If the original argument to this method was a string, we need
107
+			// to also make sure that it stays that way, so the commit function
108
+			// knows how to format the arguments to the callback.
109
+			is_string($properties) ? $properties : $usedProperties,
110
+			$callback
111
+		];
112
+
113
+	}
114
+
115
+	/**
116
+	 * Call this function if you wish to handle _all_ properties that haven't
117
+	 * been handled by anything else yet. Note that you effectively claim with
118
+	 * this that you promise to process _all_ properties that are coming in.
119
+	 *
120
+	 * @param callable $callback
121
+	 * @return void
122
+	 */
123
+	public function handleRemaining(callable $callback) {
124
+
125
+		$properties = $this->getRemainingMutations();
126
+		if (!$properties) {
127
+			// Nothing to do, don't register callback
128
+			return;
129
+		}
130
+
131
+		foreach ($properties as $propertyName) {
132
+			// HTTP Accepted
133
+			$this->result[$propertyName] = 202;
134
+
135
+			$this->propertyUpdateCallbacks[] = [
136
+				$properties,
137
+				$callback
138
+			];
139
+		}
140
+
141
+	}
142
+
143
+	/**
144
+	 * Sets the result code for one or more properties.
145
+	 *
146
+	 * @param string|string[] $properties
147
+	 * @param int $resultCode
148
+	 * @return void
149
+	 */
150
+	public function setResultCode($properties, $resultCode) {
151
+
152
+		foreach ((array)$properties as $propertyName) {
153
+			$this->result[$propertyName] = $resultCode;
154
+		}
155
+
156
+		if ($resultCode >= 400) {
157
+			$this->failed = true;
158
+		}
159
+
160
+	}
161
+
162
+	/**
163
+	 * Sets the result code for all properties that did not have a result yet.
164
+	 *
165
+	 * @param int $resultCode
166
+	 * @return void
167
+	 */
168
+	public function setRemainingResultCode($resultCode) {
169
+
170
+		$this->setResultCode(
171
+			$this->getRemainingMutations(),
172
+			$resultCode
173
+		);
174
+
175
+	}
176
+
177
+	/**
178
+	 * Returns the list of properties that don't have a result code yet.
179
+	 *
180
+	 * This method returns a list of property names, but not its values.
181
+	 *
182
+	 * @return string[]
183
+	 */
184
+	public function getRemainingMutations() {
185
+
186
+		$remaining = [];
187
+		foreach ($this->mutations as $propertyName => $propValue) {
188
+			if (!isset($this->result[$propertyName])) {
189
+				$remaining[] = $propertyName;
190
+			}
191
+		}
192
+
193
+		return $remaining;
194
+
195
+	}
196
+
197
+	/**
198
+	 * Returns the list of properties that don't have a result code yet.
199
+	 *
200
+	 * This method returns list of properties and their values.
201
+	 *
202
+	 * @return array
203
+	 */
204
+	public function getRemainingValues() {
205
+
206
+		$remaining = [];
207
+		foreach ($this->mutations as $propertyName => $propValue) {
208
+			if (!isset($this->result[$propertyName])) {
209
+				$remaining[$propertyName] = $propValue;
210
+			}
211
+		}
212
+
213
+		return $remaining;
214
+
215
+	}
216
+
217
+	/**
218
+	 * Performs the actual update, and calls all callbacks.
219
+	 *
220
+	 * This method returns true or false depending on if the operation was
221
+	 * successful.
222
+	 *
223
+	 * @return bool
224
+	 */
225
+	public function commit() {
226
+
227
+		// First we validate if every property has a handler
228
+		foreach ($this->mutations as $propertyName => $value) {
229
+
230
+			if (!isset($this->result[$propertyName])) {
231
+				$this->failed = true;
232
+				$this->result[$propertyName] = 403;
233
+			}
234
+
235
+		}
236
+
237
+		foreach ($this->propertyUpdateCallbacks as $callbackInfo) {
238
+
239
+			if ($this->failed) {
240
+				break;
241
+			}
242
+			if (is_string($callbackInfo[0])) {
243
+				$this->doCallbackSingleProp($callbackInfo[0], $callbackInfo[1]);
244
+			} else {
245
+				$this->doCallbackMultiProp($callbackInfo[0], $callbackInfo[1]);
246
+			}
247
+
248
+		}
249
+
250
+		/**
251
+		 * If anywhere in this operation updating a property failed, we must
252
+		 * update all other properties accordingly.
253
+		 */
254
+		if ($this->failed) {
255
+
256
+			foreach ($this->result as $propertyName => $status) {
257
+				if ($status === 202) {
258
+					// Failed dependency
259
+					$this->result[$propertyName] = 424;
260
+				}
261
+			}
262
+
263
+		}
264
+
265
+		return !$this->failed;
266
+
267
+	}
268
+
269
+	/**
270
+	 * Executes a property callback with the single-property syntax.
271
+	 *
272
+	 * @param string $propertyName
273
+	 * @param callable $callback
274
+	 * @return void
275
+	 */
276
+	private function doCallBackSingleProp($propertyName, callable $callback) {
277
+
278
+		$result = $callback($this->mutations[$propertyName]);
279
+		if (is_bool($result)) {
280
+			if ($result) {
281
+				if (is_null($this->mutations[$propertyName])) {
282
+					// Delete
283
+					$result = 204;
284
+				} else {
285
+					// Update
286
+					$result = 200;
287
+				}
288
+			} else {
289
+				// Fail
290
+				$result = 403;
291
+			}
292
+		}
293
+		if (!is_int($result)) {
294
+			throw new UnexpectedValueException('A callback sent to handle() did not return an int or a bool');
295
+		}
296
+		$this->result[$propertyName] = $result;
297
+		if ($result >= 400) {
298
+			$this->failed = true;
299
+		}
300
+
301
+	}
302
+
303
+	/**
304
+	 * Executes a property callback with the multi-property syntax.
305
+	 *
306
+	 * @param array $propertyList
307
+	 * @param callable $callback
308
+	 * @return void
309
+	 */
310
+	private function doCallBackMultiProp(array $propertyList, callable $callback) {
311
+
312
+		$argument = [];
313
+		foreach ($propertyList as $propertyName) {
314
+			$argument[$propertyName] = $this->mutations[$propertyName];
315
+		}
316
+
317
+		$result = $callback($argument);
318
+
319
+		if (is_array($result)) {
320
+			foreach ($propertyList as $propertyName) {
321
+				if (!isset($result[$propertyName])) {
322
+					$resultCode = 500;
323
+				} else {
324
+					$resultCode = $result[$propertyName];
325
+				}
326
+				if ($resultCode >= 400) {
327
+					$this->failed = true;
328
+				}
329
+				$this->result[$propertyName] = $resultCode;
330
+
331
+			}
332
+		} elseif ($result === true) {
333
+
334
+			// Success
335
+			foreach ($argument as $propertyName => $propertyValue) {
336
+				$this->result[$propertyName] = is_null($propertyValue) ? 204 : 200;
337
+			}
338
+
339
+		} elseif ($result === false) {
340
+			// Fail :(
341
+			$this->failed = true;
342
+			foreach ($propertyList as $propertyName) {
343
+				$this->result[$propertyName] = 403;
344
+			}
345
+		} else {
346
+			throw new UnexpectedValueException('A callback sent to handle() did not return an array or a bool');
347
+		}
348
+
349
+	}
350
+
351
+	/**
352
+	 * Returns the result of the operation.
353
+	 *
354
+	 * @return array
355
+	 */
356
+	public function getResult() {
357
+
358
+		return $this->result;
359
+
360
+	}
361
+
362
+	/**
363
+	 * Returns the full list of mutations
364
+	 *
365
+	 * @return array
366
+	 */
367
+	public function getMutations() {
368
+
369
+		return $this->mutations;
370
+
371
+	}
372 372
 
373 373
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/DAV/Server.php 1 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.
libraries/SabreDAV/DAV/SimpleFile.php 1 patch
Indentation   +102 added lines, -102 removed lines patch added patch discarded remove patch
@@ -15,107 +15,107 @@
 block discarded – undo
15 15
  */
16 16
 class SimpleFile extends File {
17 17
 
18
-    /**
19
-     * File contents
20
-     *
21
-     * @var string
22
-     */
23
-    protected $contents = [];
24
-
25
-    /**
26
-     * Name of this resource
27
-     *
28
-     * @var string
29
-     */
30
-    protected $name;
31
-
32
-    /**
33
-     * A mimetype, such as 'text/plain' or 'text/html'
34
-     *
35
-     * @var string
36
-     */
37
-    protected $mimeType;
38
-
39
-    /**
40
-     * Creates this node
41
-     *
42
-     * The name of the node must be passed, as well as the contents of the
43
-     * file.
44
-     *
45
-     * @param string $name
46
-     * @param string $contents
47
-     * @param string|null $mimeType
48
-     */
49
-    public function __construct($name, $contents, $mimeType = null) {
50
-
51
-        $this->name = $name;
52
-        $this->contents = $contents;
53
-        $this->mimeType = $mimeType;
54
-
55
-    }
56
-
57
-    /**
58
-     * Returns the node name for this file.
59
-     *
60
-     * This name is used to construct the url.
61
-     *
62
-     * @return string
63
-     */
64
-    public function getName() {
65
-
66
-        return $this->name;
67
-
68
-    }
69
-
70
-    /**
71
-     * Returns the data
72
-     *
73
-     * This method may either return a string or a readable stream resource
74
-     *
75
-     * @return mixed
76
-     */
77
-    public function get() {
78
-
79
-        return $this->contents;
80
-
81
-    }
82
-
83
-    /**
84
-     * Returns the size of the file, in bytes.
85
-     *
86
-     * @return int
87
-     */
88
-    public function getSize() {
89
-
90
-        return strlen($this->contents);
91
-
92
-    }
93
-
94
-    /**
95
-     * Returns the ETag for a file
96
-     *
97
-     * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change.
98
-     * The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
99
-     *
100
-     * Return null if the ETag can not effectively be determined
101
-     * @return string
102
-     */
103
-    public function getETag() {
104
-
105
-        return '"' . sha1($this->contents) . '"';
106
-
107
-    }
108
-
109
-    /**
110
-     * Returns the mime-type for a file
111
-     *
112
-     * If null is returned, we'll assume application/octet-stream
113
-     * @return string
114
-     */
115
-    public function getContentType() {
116
-
117
-        return $this->mimeType;
118
-
119
-    }
18
+	/**
19
+	 * File contents
20
+	 *
21
+	 * @var string
22
+	 */
23
+	protected $contents = [];
24
+
25
+	/**
26
+	 * Name of this resource
27
+	 *
28
+	 * @var string
29
+	 */
30
+	protected $name;
31
+
32
+	/**
33
+	 * A mimetype, such as 'text/plain' or 'text/html'
34
+	 *
35
+	 * @var string
36
+	 */
37
+	protected $mimeType;
38
+
39
+	/**
40
+	 * Creates this node
41
+	 *
42
+	 * The name of the node must be passed, as well as the contents of the
43
+	 * file.
44
+	 *
45
+	 * @param string $name
46
+	 * @param string $contents
47
+	 * @param string|null $mimeType
48
+	 */
49
+	public function __construct($name, $contents, $mimeType = null) {
50
+
51
+		$this->name = $name;
52
+		$this->contents = $contents;
53
+		$this->mimeType = $mimeType;
54
+
55
+	}
56
+
57
+	/**
58
+	 * Returns the node name for this file.
59
+	 *
60
+	 * This name is used to construct the url.
61
+	 *
62
+	 * @return string
63
+	 */
64
+	public function getName() {
65
+
66
+		return $this->name;
67
+
68
+	}
69
+
70
+	/**
71
+	 * Returns the data
72
+	 *
73
+	 * This method may either return a string or a readable stream resource
74
+	 *
75
+	 * @return mixed
76
+	 */
77
+	public function get() {
78
+
79
+		return $this->contents;
80
+
81
+	}
82
+
83
+	/**
84
+	 * Returns the size of the file, in bytes.
85
+	 *
86
+	 * @return int
87
+	 */
88
+	public function getSize() {
89
+
90
+		return strlen($this->contents);
91
+
92
+	}
93
+
94
+	/**
95
+	 * Returns the ETag for a file
96
+	 *
97
+	 * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change.
98
+	 * The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
99
+	 *
100
+	 * Return null if the ETag can not effectively be determined
101
+	 * @return string
102
+	 */
103
+	public function getETag() {
104
+
105
+		return '"' . sha1($this->contents) . '"';
106
+
107
+	}
108
+
109
+	/**
110
+	 * Returns the mime-type for a file
111
+	 *
112
+	 * If null is returned, we'll assume application/octet-stream
113
+	 * @return string
114
+	 */
115
+	public function getContentType() {
116
+
117
+		return $this->mimeType;
118
+
119
+	}
120 120
 
121 121
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/DAV/PropFind.php 1 patch
Indentation   +333 added lines, -333 removed lines patch added patch discarded remove patch
@@ -10,338 +10,338 @@
 block discarded – undo
10 10
  */
11 11
 class PropFind {
12 12
 
13
-    /**
14
-     * A normal propfind
15
-     */
16
-    const NORMAL = 0;
17
-
18
-    /**
19
-     * An allprops request.
20
-     *
21
-     * While this was originally intended for instructing the server to really
22
-     * fetch every property, because it was used so often and it's so heavy
23
-     * this turned into a small list of default properties after a while.
24
-     *
25
-     * So 'all properties' now means a hardcoded list.
26
-     */
27
-    const ALLPROPS = 1;
28
-
29
-    /**
30
-     * A propname request. This just returns a list of properties that are
31
-     * defined on a node, without their values.
32
-     */
33
-    const PROPNAME = 2;
34
-
35
-    /**
36
-     * Creates the PROPFIND object
37
-     *
38
-     * @param string $path
39
-     * @param array $properties
40
-     * @param int $depth
41
-     * @param int $requestType
42
-     */
43
-    public function __construct($path, array $properties, $depth = 0, $requestType = self::NORMAL) {
44
-
45
-        $this->path = $path;
46
-        $this->properties = $properties;
47
-        $this->depth = $depth;
48
-        $this->requestType = $requestType;
49
-
50
-        if ($requestType === self::ALLPROPS) {
51
-            $this->properties = [
52
-                '{DAV:}getlastmodified',
53
-                '{DAV:}getcontentlength',
54
-                '{DAV:}resourcetype',
55
-                '{DAV:}quota-used-bytes',
56
-                '{DAV:}quota-available-bytes',
57
-                '{DAV:}getetag',
58
-                '{DAV:}getcontenttype',
59
-            ];
60
-        }
61
-
62
-        foreach ($this->properties as $propertyName) {
63
-
64
-            // Seeding properties with 404's.
65
-            $this->result[$propertyName] = [404, null];
66
-
67
-        }
68
-        $this->itemsLeft = count($this->result);
69
-
70
-    }
71
-
72
-    /**
73
-     * Handles a specific property.
74
-     *
75
-     * This method checks wether the specified property was requested in this
76
-     * PROPFIND request, and if so, it will call the callback and use the
77
-     * return value for it's value.
78
-     *
79
-     * Example:
80
-     *
81
-     * $propFind->handle('{DAV:}displayname', function() {
82
-     *      return 'hello';
83
-     * });
84
-     *
85
-     * Note that handle will only work the first time. If null is returned, the
86
-     * value is ignored.
87
-     *
88
-     * It's also possible to not pass a callback, but immediately pass a value
89
-     *
90
-     * @param string $propertyName
91
-     * @param mixed $valueOrCallBack
92
-     * @return void
93
-     */
94
-    public function handle($propertyName, $valueOrCallBack) {
95
-
96
-        if ($this->itemsLeft && isset($this->result[$propertyName]) && $this->result[$propertyName][0] === 404) {
97
-            if (is_callable($valueOrCallBack)) {
98
-                $value = $valueOrCallBack();
99
-            } else {
100
-                $value = $valueOrCallBack;
101
-            }
102
-            if (!is_null($value)) {
103
-                $this->itemsLeft--;
104
-                $this->result[$propertyName] = [200, $value];
105
-            }
106
-        }
107
-
108
-    }
109
-
110
-    /**
111
-     * Sets the value of the property
112
-     *
113
-     * If status is not supplied, the status will default to 200 for non-null
114
-     * properties, and 404 for null properties.
115
-     *
116
-     * @param string $propertyName
117
-     * @param mixed $value
118
-     * @param int $status
119
-     * @return void
120
-     */
121
-    public function set($propertyName, $value, $status = null) {
122
-
123
-        if (is_null($status)) {
124
-            $status = is_null($value) ? 404 : 200;
125
-        }
126
-        // If this is an ALLPROPS request and the property is
127
-        // unknown, add it to the result; else ignore it:
128
-        if (!isset($this->result[$propertyName])) {
129
-            if ($this->requestType === self::ALLPROPS) {
130
-                $this->result[$propertyName] = [$status, $value];
131
-            }
132
-            return;
133
-        }
134
-        if ($status !== 404 && $this->result[$propertyName][0] === 404) {
135
-            $this->itemsLeft--;
136
-        } elseif ($status === 404 && $this->result[$propertyName][0] !== 404) {
137
-            $this->itemsLeft++;
138
-        }
139
-        $this->result[$propertyName] = [$status, $value];
140
-
141
-    }
142
-
143
-    /**
144
-     * Returns the current value for a property.
145
-     *
146
-     * @param string $propertyName
147
-     * @return mixed
148
-     */
149
-    public function get($propertyName) {
150
-
151
-        return isset($this->result[$propertyName]) ? $this->result[$propertyName][1] : null;
152
-
153
-    }
154
-
155
-    /**
156
-     * Returns the current status code for a property name.
157
-     *
158
-     * If the property does not appear in the list of requested properties,
159
-     * null will be returned.
160
-     *
161
-     * @param string $propertyName
162
-     * @return int|null
163
-     */
164
-    public function getStatus($propertyName) {
165
-
166
-        return isset($this->result[$propertyName]) ? $this->result[$propertyName][0] : null;
167
-
168
-    }
169
-
170
-    /**
171
-     * Updates the path for this PROPFIND.
172
-     *
173
-     * @param string $path
174
-     * @return void
175
-     */
176
-    public function setPath($path) {
177
-
178
-        $this->path = $path;
179
-
180
-    }
181
-
182
-    /**
183
-     * Returns the path this PROPFIND request is for.
184
-     *
185
-     * @return string
186
-     */
187
-    public function getPath() {
188
-
189
-        return $this->path;
190
-
191
-    }
192
-
193
-    /**
194
-     * Returns the depth of this propfind request.
195
-     *
196
-     * @return int
197
-     */
198
-    public function getDepth() {
199
-
200
-        return $this->depth;
201
-
202
-    }
203
-
204
-    /**
205
-     * Updates the depth of this propfind request.
206
-     *
207
-     * @param int $depth
208
-     * @return void
209
-     */
210
-    public function setDepth($depth) {
211
-
212
-        $this->depth = $depth;
213
-
214
-    }
215
-
216
-    /**
217
-     * Returns all propertynames that have a 404 status, and thus don't have a
218
-     * value yet.
219
-     *
220
-     * @return array
221
-     */
222
-    public function get404Properties() {
223
-
224
-        if ($this->itemsLeft === 0) {
225
-            return [];
226
-        }
227
-        $result = [];
228
-        foreach ($this->result as $propertyName => $stuff) {
229
-            if ($stuff[0] === 404) {
230
-                $result[] = $propertyName;
231
-            }
232
-        }
233
-        return $result;
234
-
235
-    }
236
-
237
-    /**
238
-     * Returns the full list of requested properties.
239
-     *
240
-     * This returns just their names, not a status or value.
241
-     *
242
-     * @return array
243
-     */
244
-    public function getRequestedProperties() {
245
-
246
-        return $this->properties;
247
-
248
-    }
249
-
250
-    /**
251
-     * Returns true if this was an '{DAV:}allprops' request.
252
-     *
253
-     * @return bool
254
-     */
255
-    public function isAllProps() {
256
-
257
-        return $this->requestType === self::ALLPROPS;
258
-
259
-    }
260
-
261
-    /**
262
-     * Returns a result array that's often used in multistatus responses.
263
-     *
264
-     * The array uses status codes as keys, and property names and value pairs
265
-     * as the value of the top array.. such as :
266
-     *
267
-     * [
268
-     *  200 => [ '{DAV:}displayname' => 'foo' ],
269
-     * ]
270
-     *
271
-     * @return array
272
-     */
273
-    public function getResultForMultiStatus() {
274
-
275
-        $r = [
276
-            200 => [],
277
-            404 => [],
278
-        ];
279
-        foreach ($this->result as $propertyName => $info) {
280
-            if (!isset($r[$info[0]])) {
281
-                $r[$info[0]] = [$propertyName => $info[1]];
282
-            } else {
283
-                $r[$info[0]][$propertyName] = $info[1];
284
-            }
285
-        }
286
-        // Removing the 404's for multi-status requests.
287
-        if ($this->requestType === self::ALLPROPS) unset($r[404]);
288
-        return $r;
289
-
290
-    }
291
-
292
-    /**
293
-     * The path that we're fetching properties for.
294
-     *
295
-     * @var string
296
-     */
297
-    protected $path;
298
-
299
-    /**
300
-     * The Depth of the request.
301
-     *
302
-     * 0 means only the current item. 1 means the current item + its children.
303
-     * It can also be DEPTH_INFINITY if this is enabled in the server.
304
-     *
305
-     * @var int
306
-     */
307
-    protected $depth = 0;
308
-
309
-    /**
310
-     * The type of request. See the TYPE constants
311
-     */
312
-    protected $requestType;
313
-
314
-    /**
315
-     * A list of requested properties
316
-     *
317
-     * @var array
318
-     */
319
-    protected $properties = [];
320
-
321
-    /**
322
-     * The result of the operation.
323
-     *
324
-     * The keys in this array are property names.
325
-     * The values are an array with two elements: the http status code and then
326
-     * optionally a value.
327
-     *
328
-     * Example:
329
-     *
330
-     * [
331
-     *    "{DAV:}owner" : [404],
332
-     *    "{DAV:}displayname" : [200, "Admin"]
333
-     * ]
334
-     *
335
-     * @var array
336
-     */
337
-    protected $result = [];
338
-
339
-    /**
340
-     * This is used as an internal counter for the number of properties that do
341
-     * not yet have a value.
342
-     *
343
-     * @var int
344
-     */
345
-    protected $itemsLeft;
13
+	/**
14
+	 * A normal propfind
15
+	 */
16
+	const NORMAL = 0;
17
+
18
+	/**
19
+	 * An allprops request.
20
+	 *
21
+	 * While this was originally intended for instructing the server to really
22
+	 * fetch every property, because it was used so often and it's so heavy
23
+	 * this turned into a small list of default properties after a while.
24
+	 *
25
+	 * So 'all properties' now means a hardcoded list.
26
+	 */
27
+	const ALLPROPS = 1;
28
+
29
+	/**
30
+	 * A propname request. This just returns a list of properties that are
31
+	 * defined on a node, without their values.
32
+	 */
33
+	const PROPNAME = 2;
34
+
35
+	/**
36
+	 * Creates the PROPFIND object
37
+	 *
38
+	 * @param string $path
39
+	 * @param array $properties
40
+	 * @param int $depth
41
+	 * @param int $requestType
42
+	 */
43
+	public function __construct($path, array $properties, $depth = 0, $requestType = self::NORMAL) {
44
+
45
+		$this->path = $path;
46
+		$this->properties = $properties;
47
+		$this->depth = $depth;
48
+		$this->requestType = $requestType;
49
+
50
+		if ($requestType === self::ALLPROPS) {
51
+			$this->properties = [
52
+				'{DAV:}getlastmodified',
53
+				'{DAV:}getcontentlength',
54
+				'{DAV:}resourcetype',
55
+				'{DAV:}quota-used-bytes',
56
+				'{DAV:}quota-available-bytes',
57
+				'{DAV:}getetag',
58
+				'{DAV:}getcontenttype',
59
+			];
60
+		}
61
+
62
+		foreach ($this->properties as $propertyName) {
63
+
64
+			// Seeding properties with 404's.
65
+			$this->result[$propertyName] = [404, null];
66
+
67
+		}
68
+		$this->itemsLeft = count($this->result);
69
+
70
+	}
71
+
72
+	/**
73
+	 * Handles a specific property.
74
+	 *
75
+	 * This method checks wether the specified property was requested in this
76
+	 * PROPFIND request, and if so, it will call the callback and use the
77
+	 * return value for it's value.
78
+	 *
79
+	 * Example:
80
+	 *
81
+	 * $propFind->handle('{DAV:}displayname', function() {
82
+	 *      return 'hello';
83
+	 * });
84
+	 *
85
+	 * Note that handle will only work the first time. If null is returned, the
86
+	 * value is ignored.
87
+	 *
88
+	 * It's also possible to not pass a callback, but immediately pass a value
89
+	 *
90
+	 * @param string $propertyName
91
+	 * @param mixed $valueOrCallBack
92
+	 * @return void
93
+	 */
94
+	public function handle($propertyName, $valueOrCallBack) {
95
+
96
+		if ($this->itemsLeft && isset($this->result[$propertyName]) && $this->result[$propertyName][0] === 404) {
97
+			if (is_callable($valueOrCallBack)) {
98
+				$value = $valueOrCallBack();
99
+			} else {
100
+				$value = $valueOrCallBack;
101
+			}
102
+			if (!is_null($value)) {
103
+				$this->itemsLeft--;
104
+				$this->result[$propertyName] = [200, $value];
105
+			}
106
+		}
107
+
108
+	}
109
+
110
+	/**
111
+	 * Sets the value of the property
112
+	 *
113
+	 * If status is not supplied, the status will default to 200 for non-null
114
+	 * properties, and 404 for null properties.
115
+	 *
116
+	 * @param string $propertyName
117
+	 * @param mixed $value
118
+	 * @param int $status
119
+	 * @return void
120
+	 */
121
+	public function set($propertyName, $value, $status = null) {
122
+
123
+		if (is_null($status)) {
124
+			$status = is_null($value) ? 404 : 200;
125
+		}
126
+		// If this is an ALLPROPS request and the property is
127
+		// unknown, add it to the result; else ignore it:
128
+		if (!isset($this->result[$propertyName])) {
129
+			if ($this->requestType === self::ALLPROPS) {
130
+				$this->result[$propertyName] = [$status, $value];
131
+			}
132
+			return;
133
+		}
134
+		if ($status !== 404 && $this->result[$propertyName][0] === 404) {
135
+			$this->itemsLeft--;
136
+		} elseif ($status === 404 && $this->result[$propertyName][0] !== 404) {
137
+			$this->itemsLeft++;
138
+		}
139
+		$this->result[$propertyName] = [$status, $value];
140
+
141
+	}
142
+
143
+	/**
144
+	 * Returns the current value for a property.
145
+	 *
146
+	 * @param string $propertyName
147
+	 * @return mixed
148
+	 */
149
+	public function get($propertyName) {
150
+
151
+		return isset($this->result[$propertyName]) ? $this->result[$propertyName][1] : null;
152
+
153
+	}
154
+
155
+	/**
156
+	 * Returns the current status code for a property name.
157
+	 *
158
+	 * If the property does not appear in the list of requested properties,
159
+	 * null will be returned.
160
+	 *
161
+	 * @param string $propertyName
162
+	 * @return int|null
163
+	 */
164
+	public function getStatus($propertyName) {
165
+
166
+		return isset($this->result[$propertyName]) ? $this->result[$propertyName][0] : null;
167
+
168
+	}
169
+
170
+	/**
171
+	 * Updates the path for this PROPFIND.
172
+	 *
173
+	 * @param string $path
174
+	 * @return void
175
+	 */
176
+	public function setPath($path) {
177
+
178
+		$this->path = $path;
179
+
180
+	}
181
+
182
+	/**
183
+	 * Returns the path this PROPFIND request is for.
184
+	 *
185
+	 * @return string
186
+	 */
187
+	public function getPath() {
188
+
189
+		return $this->path;
190
+
191
+	}
192
+
193
+	/**
194
+	 * Returns the depth of this propfind request.
195
+	 *
196
+	 * @return int
197
+	 */
198
+	public function getDepth() {
199
+
200
+		return $this->depth;
201
+
202
+	}
203
+
204
+	/**
205
+	 * Updates the depth of this propfind request.
206
+	 *
207
+	 * @param int $depth
208
+	 * @return void
209
+	 */
210
+	public function setDepth($depth) {
211
+
212
+		$this->depth = $depth;
213
+
214
+	}
215
+
216
+	/**
217
+	 * Returns all propertynames that have a 404 status, and thus don't have a
218
+	 * value yet.
219
+	 *
220
+	 * @return array
221
+	 */
222
+	public function get404Properties() {
223
+
224
+		if ($this->itemsLeft === 0) {
225
+			return [];
226
+		}
227
+		$result = [];
228
+		foreach ($this->result as $propertyName => $stuff) {
229
+			if ($stuff[0] === 404) {
230
+				$result[] = $propertyName;
231
+			}
232
+		}
233
+		return $result;
234
+
235
+	}
236
+
237
+	/**
238
+	 * Returns the full list of requested properties.
239
+	 *
240
+	 * This returns just their names, not a status or value.
241
+	 *
242
+	 * @return array
243
+	 */
244
+	public function getRequestedProperties() {
245
+
246
+		return $this->properties;
247
+
248
+	}
249
+
250
+	/**
251
+	 * Returns true if this was an '{DAV:}allprops' request.
252
+	 *
253
+	 * @return bool
254
+	 */
255
+	public function isAllProps() {
256
+
257
+		return $this->requestType === self::ALLPROPS;
258
+
259
+	}
260
+
261
+	/**
262
+	 * Returns a result array that's often used in multistatus responses.
263
+	 *
264
+	 * The array uses status codes as keys, and property names and value pairs
265
+	 * as the value of the top array.. such as :
266
+	 *
267
+	 * [
268
+	 *  200 => [ '{DAV:}displayname' => 'foo' ],
269
+	 * ]
270
+	 *
271
+	 * @return array
272
+	 */
273
+	public function getResultForMultiStatus() {
274
+
275
+		$r = [
276
+			200 => [],
277
+			404 => [],
278
+		];
279
+		foreach ($this->result as $propertyName => $info) {
280
+			if (!isset($r[$info[0]])) {
281
+				$r[$info[0]] = [$propertyName => $info[1]];
282
+			} else {
283
+				$r[$info[0]][$propertyName] = $info[1];
284
+			}
285
+		}
286
+		// Removing the 404's for multi-status requests.
287
+		if ($this->requestType === self::ALLPROPS) unset($r[404]);
288
+		return $r;
289
+
290
+	}
291
+
292
+	/**
293
+	 * The path that we're fetching properties for.
294
+	 *
295
+	 * @var string
296
+	 */
297
+	protected $path;
298
+
299
+	/**
300
+	 * The Depth of the request.
301
+	 *
302
+	 * 0 means only the current item. 1 means the current item + its children.
303
+	 * It can also be DEPTH_INFINITY if this is enabled in the server.
304
+	 *
305
+	 * @var int
306
+	 */
307
+	protected $depth = 0;
308
+
309
+	/**
310
+	 * The type of request. See the TYPE constants
311
+	 */
312
+	protected $requestType;
313
+
314
+	/**
315
+	 * A list of requested properties
316
+	 *
317
+	 * @var array
318
+	 */
319
+	protected $properties = [];
320
+
321
+	/**
322
+	 * The result of the operation.
323
+	 *
324
+	 * The keys in this array are property names.
325
+	 * The values are an array with two elements: the http status code and then
326
+	 * optionally a value.
327
+	 *
328
+	 * Example:
329
+	 *
330
+	 * [
331
+	 *    "{DAV:}owner" : [404],
332
+	 *    "{DAV:}displayname" : [200, "Admin"]
333
+	 * ]
334
+	 *
335
+	 * @var array
336
+	 */
337
+	protected $result = [];
338
+
339
+	/**
340
+	 * This is used as an internal counter for the number of properties that do
341
+	 * not yet have a value.
342
+	 *
343
+	 * @var int
344
+	 */
345
+	protected $itemsLeft;
346 346
 
347 347
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/DAV/FS/File.php 1 patch
Indentation   +78 added lines, -78 removed lines patch added patch discarded remove patch
@@ -13,83 +13,83 @@
 block discarded – undo
13 13
  */
14 14
 class File extends Node implements DAV\IFile {
15 15
 
16
-    /**
17
-     * Updates the data
18
-     *
19
-     * @param resource $data
20
-     * @return void
21
-     */
22
-    public function put($data) {
23
-
24
-        file_put_contents($this->path, $data);
25
-        clearstatcache(true, $this->path);
26
-
27
-    }
28
-
29
-    /**
30
-     * Returns the data
31
-     *
32
-     * @return resource
33
-     */
34
-    public function get() {
35
-
36
-        return fopen($this->path, 'r');
37
-
38
-    }
39
-
40
-    /**
41
-     * Delete the current file
42
-     *
43
-     * @return void
44
-     */
45
-    public function delete() {
46
-
47
-        unlink($this->path);
48
-
49
-    }
50
-
51
-    /**
52
-     * Returns the size of the node, in bytes
53
-     *
54
-     * @return int
55
-     */
56
-    public function getSize() {
57
-
58
-        return filesize($this->path);
59
-
60
-    }
61
-
62
-    /**
63
-     * Returns the ETag for a file
64
-     *
65
-     * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change.
66
-     * The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
67
-     *
68
-     * Return null if the ETag can not effectively be determined
69
-     *
70
-     * @return mixed
71
-     */
72
-    public function getETag() {
73
-
74
-        return '"' . sha1(
75
-            fileinode($this->path) .
76
-            filesize($this->path) .
77
-            filemtime($this->path)
78
-        ) . '"';
79
-
80
-    }
81
-
82
-    /**
83
-     * Returns the mime-type for a file
84
-     *
85
-     * If null is returned, we'll assume application/octet-stream
86
-     *
87
-     * @return mixed
88
-     */
89
-    public function getContentType() {
90
-
91
-        return null;
92
-
93
-    }
16
+	/**
17
+	 * Updates the data
18
+	 *
19
+	 * @param resource $data
20
+	 * @return void
21
+	 */
22
+	public function put($data) {
23
+
24
+		file_put_contents($this->path, $data);
25
+		clearstatcache(true, $this->path);
26
+
27
+	}
28
+
29
+	/**
30
+	 * Returns the data
31
+	 *
32
+	 * @return resource
33
+	 */
34
+	public function get() {
35
+
36
+		return fopen($this->path, 'r');
37
+
38
+	}
39
+
40
+	/**
41
+	 * Delete the current file
42
+	 *
43
+	 * @return void
44
+	 */
45
+	public function delete() {
46
+
47
+		unlink($this->path);
48
+
49
+	}
50
+
51
+	/**
52
+	 * Returns the size of the node, in bytes
53
+	 *
54
+	 * @return int
55
+	 */
56
+	public function getSize() {
57
+
58
+		return filesize($this->path);
59
+
60
+	}
61
+
62
+	/**
63
+	 * Returns the ETag for a file
64
+	 *
65
+	 * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change.
66
+	 * The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
67
+	 *
68
+	 * Return null if the ETag can not effectively be determined
69
+	 *
70
+	 * @return mixed
71
+	 */
72
+	public function getETag() {
73
+
74
+		return '"' . sha1(
75
+			fileinode($this->path) .
76
+			filesize($this->path) .
77
+			filemtime($this->path)
78
+		) . '"';
79
+
80
+	}
81
+
82
+	/**
83
+	 * Returns the mime-type for a file
84
+	 *
85
+	 * If null is returned, we'll assume application/octet-stream
86
+	 *
87
+	 * @return mixed
88
+	 */
89
+	public function getContentType() {
90
+
91
+		return null;
92
+
93
+	}
94 94
 
95 95
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/DAV/FS/Directory.php 1 patch
Indentation   +131 added lines, -131 removed lines patch added patch discarded remove patch
@@ -13,139 +13,139 @@
 block discarded – undo
13 13
  */
14 14
 class Directory extends Node implements DAV\ICollection, DAV\IQuota {
15 15
 
16
-    /**
17
-     * Creates a new file in the directory
18
-     *
19
-     * Data will either be supplied as a stream resource, or in certain cases
20
-     * as a string. Keep in mind that you may have to support either.
21
-     *
22
-     * After successful creation of the file, you may choose to return the ETag
23
-     * of the new file here.
24
-     *
25
-     * The returned ETag must be surrounded by double-quotes (The quotes should
26
-     * be part of the actual string).
27
-     *
28
-     * If you cannot accurately determine the ETag, you should not return it.
29
-     * If you don't store the file exactly as-is (you're transforming it
30
-     * somehow) you should also not return an ETag.
31
-     *
32
-     * This means that if a subsequent GET to this new file does not exactly
33
-     * return the same contents of what was submitted here, you are strongly
34
-     * recommended to omit the ETag.
35
-     *
36
-     * @param string $name Name of the file
37
-     * @param resource|string $data Initial payload
38
-     * @return null|string
39
-     */
40
-    public function createFile($name, $data = null) {
41
-
42
-        $newPath = $this->path . '/' . $name;
43
-        file_put_contents($newPath, $data);
44
-        clearstatcache(true, $newPath);
45
-
46
-    }
47
-
48
-    /**
49
-     * Creates a new subdirectory
50
-     *
51
-     * @param string $name
52
-     * @return void
53
-     */
54
-    public function createDirectory($name) {
55
-
56
-        $newPath = $this->path . '/' . $name;
57
-        mkdir($newPath);
58
-        clearstatcache(true, $newPath);
59
-
60
-    }
61
-
62
-    /**
63
-     * Returns a specific child node, referenced by its name
64
-     *
65
-     * This method must throw DAV\Exception\NotFound if the node does not
66
-     * exist.
67
-     *
68
-     * @param string $name
69
-     * @throws DAV\Exception\NotFound
70
-     * @return DAV\INode
71
-     */
72
-    public function getChild($name) {
73
-
74
-        $path = $this->path . '/' . $name;
75
-
76
-        if (!file_exists($path)) throw new DAV\Exception\NotFound('File with name ' . $path . ' could not be located');
77
-
78
-        if (is_dir($path)) {
79
-
80
-            return new self($path);
81
-
82
-        } else {
83
-
84
-            return new File($path);
85
-
86
-        }
87
-
88
-    }
89
-
90
-    /**
91
-     * Returns an array with all the child nodes
92
-     *
93
-     * @return DAV\INode[]
94
-     */
95
-    public function getChildren() {
96
-
97
-        $nodes = [];
98
-        $iterator = new \FilesystemIterator(
99
-            $this->path,
100
-            \FilesystemIterator::CURRENT_AS_SELF
101
-          | \FilesystemIterator::SKIP_DOTS
102
-        );
103
-        foreach ($iterator as $entry) {
104
-
105
-            $nodes[] = $this->getChild($entry->getFilename());
106
-
107
-        }
108
-        return $nodes;
109
-
110
-    }
111
-
112
-    /**
113
-     * Checks if a child exists.
114
-     *
115
-     * @param string $name
116
-     * @return bool
117
-     */
118
-    public function childExists($name) {
119
-
120
-        $path = $this->path . '/' . $name;
121
-        return file_exists($path);
122
-
123
-    }
124
-
125
-    /**
126
-     * Deletes all files in this directory, and then itself
127
-     *
128
-     * @return void
129
-     */
130
-    public function delete() {
131
-
132
-        foreach ($this->getChildren() as $child) $child->delete();
133
-        rmdir($this->path);
134
-
135
-    }
16
+	/**
17
+	 * Creates a new file in the directory
18
+	 *
19
+	 * Data will either be supplied as a stream resource, or in certain cases
20
+	 * as a string. Keep in mind that you may have to support either.
21
+	 *
22
+	 * After successful creation of the file, you may choose to return the ETag
23
+	 * of the new file here.
24
+	 *
25
+	 * The returned ETag must be surrounded by double-quotes (The quotes should
26
+	 * be part of the actual string).
27
+	 *
28
+	 * If you cannot accurately determine the ETag, you should not return it.
29
+	 * If you don't store the file exactly as-is (you're transforming it
30
+	 * somehow) you should also not return an ETag.
31
+	 *
32
+	 * This means that if a subsequent GET to this new file does not exactly
33
+	 * return the same contents of what was submitted here, you are strongly
34
+	 * recommended to omit the ETag.
35
+	 *
36
+	 * @param string $name Name of the file
37
+	 * @param resource|string $data Initial payload
38
+	 * @return null|string
39
+	 */
40
+	public function createFile($name, $data = null) {
41
+
42
+		$newPath = $this->path . '/' . $name;
43
+		file_put_contents($newPath, $data);
44
+		clearstatcache(true, $newPath);
45
+
46
+	}
47
+
48
+	/**
49
+	 * Creates a new subdirectory
50
+	 *
51
+	 * @param string $name
52
+	 * @return void
53
+	 */
54
+	public function createDirectory($name) {
55
+
56
+		$newPath = $this->path . '/' . $name;
57
+		mkdir($newPath);
58
+		clearstatcache(true, $newPath);
59
+
60
+	}
61
+
62
+	/**
63
+	 * Returns a specific child node, referenced by its name
64
+	 *
65
+	 * This method must throw DAV\Exception\NotFound if the node does not
66
+	 * exist.
67
+	 *
68
+	 * @param string $name
69
+	 * @throws DAV\Exception\NotFound
70
+	 * @return DAV\INode
71
+	 */
72
+	public function getChild($name) {
73
+
74
+		$path = $this->path . '/' . $name;
75
+
76
+		if (!file_exists($path)) throw new DAV\Exception\NotFound('File with name ' . $path . ' could not be located');
77
+
78
+		if (is_dir($path)) {
79
+
80
+			return new self($path);
81
+
82
+		} else {
83
+
84
+			return new File($path);
85
+
86
+		}
87
+
88
+	}
89
+
90
+	/**
91
+	 * Returns an array with all the child nodes
92
+	 *
93
+	 * @return DAV\INode[]
94
+	 */
95
+	public function getChildren() {
96
+
97
+		$nodes = [];
98
+		$iterator = new \FilesystemIterator(
99
+			$this->path,
100
+			\FilesystemIterator::CURRENT_AS_SELF
101
+		  | \FilesystemIterator::SKIP_DOTS
102
+		);
103
+		foreach ($iterator as $entry) {
104
+
105
+			$nodes[] = $this->getChild($entry->getFilename());
106
+
107
+		}
108
+		return $nodes;
109
+
110
+	}
111
+
112
+	/**
113
+	 * Checks if a child exists.
114
+	 *
115
+	 * @param string $name
116
+	 * @return bool
117
+	 */
118
+	public function childExists($name) {
119
+
120
+		$path = $this->path . '/' . $name;
121
+		return file_exists($path);
122
+
123
+	}
124
+
125
+	/**
126
+	 * Deletes all files in this directory, and then itself
127
+	 *
128
+	 * @return void
129
+	 */
130
+	public function delete() {
131
+
132
+		foreach ($this->getChildren() as $child) $child->delete();
133
+		rmdir($this->path);
134
+
135
+	}
136 136
 
137
-    /**
138
-     * Returns available diskspace information
139
-     *
140
-     * @return array
141
-     */
142
-    public function getQuotaInfo() {
137
+	/**
138
+	 * Returns available diskspace information
139
+	 *
140
+	 * @return array
141
+	 */
142
+	public function getQuotaInfo() {
143 143
 
144
-        return [
145
-            disk_total_space($this->path) - disk_free_space($this->path),
146
-            disk_free_space($this->path)
147
-        ];
144
+		return [
145
+			disk_total_space($this->path) - disk_free_space($this->path),
146
+			disk_free_space($this->path)
147
+		];
148 148
 
149
-    }
149
+	}
150 150
 
151 151
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/DAV/FS/Node.php 1 patch
Indentation   +44 added lines, -44 removed lines patch added patch discarded remove patch
@@ -16,65 +16,65 @@
 block discarded – undo
16 16
  */
17 17
 abstract class Node implements DAV\INode {
18 18
 
19
-    /**
20
-     * The path to the current node
21
-     *
22
-     * @var string
23
-     */
24
-    protected $path;
19
+	/**
20
+	 * The path to the current node
21
+	 *
22
+	 * @var string
23
+	 */
24
+	protected $path;
25 25
 
26
-    /**
27
-     * Sets up the node, expects a full path name
28
-     *
29
-     * @param string $path
30
-     */
31
-    public function __construct($path) {
26
+	/**
27
+	 * Sets up the node, expects a full path name
28
+	 *
29
+	 * @param string $path
30
+	 */
31
+	public function __construct($path) {
32 32
 
33
-        $this->path = $path;
33
+		$this->path = $path;
34 34
 
35
-    }
35
+	}
36 36
 
37 37
 
38 38
 
39
-    /**
40
-     * Returns the name of the node
41
-     *
42
-     * @return string
43
-     */
44
-    public function getName() {
39
+	/**
40
+	 * Returns the name of the node
41
+	 *
42
+	 * @return string
43
+	 */
44
+	public function getName() {
45 45
 
46
-        list(, $name)  = URLUtil::splitPath($this->path);
47
-        return $name;
46
+		list(, $name)  = URLUtil::splitPath($this->path);
47
+		return $name;
48 48
 
49
-    }
49
+	}
50 50
 
51
-    /**
52
-     * Renames the node
53
-     *
54
-     * @param string $name The new name
55
-     * @return void
56
-     */
57
-    public function setName($name) {
51
+	/**
52
+	 * Renames the node
53
+	 *
54
+	 * @param string $name The new name
55
+	 * @return void
56
+	 */
57
+	public function setName($name) {
58 58
 
59
-        list($parentPath, ) = URLUtil::splitPath($this->path);
60
-        list(, $newName) = URLUtil::splitPath($name);
59
+		list($parentPath, ) = URLUtil::splitPath($this->path);
60
+		list(, $newName) = URLUtil::splitPath($name);
61 61
 
62
-        $newPath = $parentPath . '/' . $newName;
63
-        rename($this->path, $newPath);
62
+		$newPath = $parentPath . '/' . $newName;
63
+		rename($this->path, $newPath);
64 64
 
65
-        $this->path = $newPath;
65
+		$this->path = $newPath;
66 66
 
67
-    }
67
+	}
68 68
 
69
-    /**
70
-     * Returns the last modification time, as a unix timestamp
71
-     *
72
-     * @return int
73
-     */
74
-    public function getLastModified() {
69
+	/**
70
+	 * Returns the last modification time, as a unix timestamp
71
+	 *
72
+	 * @return int
73
+	 */
74
+	public function getLastModified() {
75 75
 
76
-        return filemtime($this->path);
76
+		return filemtime($this->path);
77 77
 
78
-    }
78
+	}
79 79
 
80 80
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/DAV/SimpleCollection.php 1 patch
Indentation   +88 added lines, -88 removed lines patch added patch discarded remove patch
@@ -14,94 +14,94 @@
 block discarded – undo
14 14
  */
15 15
 class SimpleCollection extends Collection {
16 16
 
17
-    /**
18
-     * List of childnodes
19
-     *
20
-     * @var INode[]
21
-     */
22
-    protected $children = [];
23
-
24
-    /**
25
-     * Name of this resource
26
-     *
27
-     * @var string
28
-     */
29
-    protected $name;
30
-
31
-    /**
32
-     * Creates this node
33
-     *
34
-     * The name of the node must be passed, child nodes can also be passed.
35
-     * This nodes must be instances of INode
36
-     *
37
-     * @param string $name
38
-     * @param INode[] $children
39
-     */
40
-    public function __construct($name, array $children = []) {
41
-
42
-        $this->name = $name;
43
-        foreach ($children as $child) {
44
-
45
-            if (!($child instanceof INode)) throw new Exception('Only instances of Sabre\DAV\INode are allowed to be passed in the children argument');
46
-            $this->addChild($child);
47
-
48
-        }
49
-
50
-    }
51
-
52
-    /**
53
-     * Adds a new childnode to this collection
54
-     *
55
-     * @param INode $child
56
-     * @return void
57
-     */
58
-    public function addChild(INode $child) {
59
-
60
-        $this->children[$child->getName()] = $child;
61
-
62
-    }
63
-
64
-    /**
65
-     * Returns the name of the collection
66
-     *
67
-     * @return string
68
-     */
69
-    public function getName() {
70
-
71
-        return $this->name;
72
-
73
-    }
74
-
75
-    /**
76
-     * Returns a child object, by its name.
77
-     *
78
-     * This method makes use of the getChildren method to grab all the child nodes, and compares the name.
79
-     * Generally its wise to override this, as this can usually be optimized
80
-     *
81
-     * This method must throw Sabre\DAV\Exception\NotFound if the node does not
82
-     * exist.
83
-     *
84
-     * @param string $name
85
-     * @throws Exception\NotFound
86
-     * @return INode
87
-     */
88
-    public function getChild($name) {
89
-
90
-        if (isset($this->children[$name])) return $this->children[$name];
91
-        throw new Exception\NotFound('File not found: ' . $name . ' in \'' . $this->getName() . '\'');
92
-
93
-    }
94
-
95
-    /**
96
-     * Returns a list of children for this collection
97
-     *
98
-     * @return INode[]
99
-     */
100
-    public function getChildren() {
101
-
102
-        return array_values($this->children);
103
-
104
-    }
17
+	/**
18
+	 * List of childnodes
19
+	 *
20
+	 * @var INode[]
21
+	 */
22
+	protected $children = [];
23
+
24
+	/**
25
+	 * Name of this resource
26
+	 *
27
+	 * @var string
28
+	 */
29
+	protected $name;
30
+
31
+	/**
32
+	 * Creates this node
33
+	 *
34
+	 * The name of the node must be passed, child nodes can also be passed.
35
+	 * This nodes must be instances of INode
36
+	 *
37
+	 * @param string $name
38
+	 * @param INode[] $children
39
+	 */
40
+	public function __construct($name, array $children = []) {
41
+
42
+		$this->name = $name;
43
+		foreach ($children as $child) {
44
+
45
+			if (!($child instanceof INode)) throw new Exception('Only instances of Sabre\DAV\INode are allowed to be passed in the children argument');
46
+			$this->addChild($child);
47
+
48
+		}
49
+
50
+	}
51
+
52
+	/**
53
+	 * Adds a new childnode to this collection
54
+	 *
55
+	 * @param INode $child
56
+	 * @return void
57
+	 */
58
+	public function addChild(INode $child) {
59
+
60
+		$this->children[$child->getName()] = $child;
61
+
62
+	}
63
+
64
+	/**
65
+	 * Returns the name of the collection
66
+	 *
67
+	 * @return string
68
+	 */
69
+	public function getName() {
70
+
71
+		return $this->name;
72
+
73
+	}
74
+
75
+	/**
76
+	 * Returns a child object, by its name.
77
+	 *
78
+	 * This method makes use of the getChildren method to grab all the child nodes, and compares the name.
79
+	 * Generally its wise to override this, as this can usually be optimized
80
+	 *
81
+	 * This method must throw Sabre\DAV\Exception\NotFound if the node does not
82
+	 * exist.
83
+	 *
84
+	 * @param string $name
85
+	 * @throws Exception\NotFound
86
+	 * @return INode
87
+	 */
88
+	public function getChild($name) {
89
+
90
+		if (isset($this->children[$name])) return $this->children[$name];
91
+		throw new Exception\NotFound('File not found: ' . $name . ' in \'' . $this->getName() . '\'');
92
+
93
+	}
94
+
95
+	/**
96
+	 * Returns a list of children for this collection
97
+	 *
98
+	 * @return INode[]
99
+	 */
100
+	public function getChildren() {
101
+
102
+		return array_values($this->children);
103
+
104
+	}
105 105
 
106 106
 
107 107
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/DAV/FSExt/File.php 1 patch
Indentation   +134 added lines, -134 removed lines patch added patch discarded remove patch
@@ -14,139 +14,139 @@
 block discarded – undo
14 14
  */
15 15
 class File extends Node implements DAV\PartialUpdate\IPatchSupport {
16 16
 
17
-    /**
18
-     * Updates the data
19
-     *
20
-     * Data is a readable stream resource.
21
-     *
22
-     * @param resource|string $data
23
-     * @return string
24
-     */
25
-    public function put($data) {
26
-
27
-        file_put_contents($this->path, $data);
28
-        clearstatcache(true, $this->path);
29
-        return $this->getETag();
30
-
31
-    }
32
-
33
-    /**
34
-     * Updates the file based on a range specification.
35
-     *
36
-     * The first argument is the data, which is either a readable stream
37
-     * resource or a string.
38
-     *
39
-     * The second argument is the type of update we're doing.
40
-     * This is either:
41
-     * * 1. append
42
-     * * 2. update based on a start byte
43
-     * * 3. update based on an end byte
44
-     *;
45
-     * The third argument is the start or end byte.
46
-     *
47
-     * After a successful put operation, you may choose to return an ETag. The
48
-     * ETAG must always be surrounded by double-quotes. These quotes must
49
-     * appear in the actual string you're returning.
50
-     *
51
-     * Clients may use the ETag from a PUT request to later on make sure that
52
-     * when they update the file, the contents haven't changed in the mean
53
-     * time.
54
-     *
55
-     * @param resource|string $data
56
-     * @param int $rangeType
57
-     * @param int $offset
58
-     * @return string|null
59
-     */
60
-    public function patch($data, $rangeType, $offset = null) {
61
-
62
-        switch ($rangeType) {
63
-            case 1 :
64
-                $f = fopen($this->path, 'a');
65
-                break;
66
-            case 2 :
67
-                $f = fopen($this->path, 'c');
68
-                fseek($f, $offset);
69
-                break;
70
-            case 3 :
71
-                $f = fopen($this->path, 'c');
72
-                fseek($f, $offset, SEEK_END);
73
-                break;
74
-        }
75
-        if (is_string($data)) {
76
-            fwrite($f, $data);
77
-        } else {
78
-            stream_copy_to_stream($data, $f);
79
-        }
80
-        fclose($f);
81
-        clearstatcache(true, $this->path);
82
-        return $this->getETag();
83
-
84
-    }
85
-
86
-    /**
87
-     * Returns the data
88
-     *
89
-     * @return resource
90
-     */
91
-    public function get() {
92
-
93
-        return fopen($this->path, 'r');
94
-
95
-    }
96
-
97
-    /**
98
-     * Delete the current file
99
-     *
100
-     * @return bool
101
-     */
102
-    public function delete() {
103
-
104
-        return unlink($this->path);
105
-
106
-    }
107
-
108
-    /**
109
-     * Returns the ETag for a file
110
-     *
111
-     * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change.
112
-     * The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
113
-     *
114
-     * Return null if the ETag can not effectively be determined
115
-     *
116
-     * @return string|null
117
-     */
118
-    public function getETag() {
119
-
120
-        return '"' . sha1(
121
-            fileinode($this->path) .
122
-            filesize($this->path) .
123
-            filemtime($this->path)
124
-        ) . '"';
125
-
126
-    }
127
-
128
-    /**
129
-     * Returns the mime-type for a file
130
-     *
131
-     * If null is returned, we'll assume application/octet-stream
132
-     *
133
-     * @return string|null
134
-     */
135
-    public function getContentType() {
136
-
137
-        return null;
138
-
139
-    }
140
-
141
-    /**
142
-     * Returns the size of the file, in bytes
143
-     *
144
-     * @return int
145
-     */
146
-    public function getSize() {
147
-
148
-        return filesize($this->path);
149
-
150
-    }
17
+	/**
18
+	 * Updates the data
19
+	 *
20
+	 * Data is a readable stream resource.
21
+	 *
22
+	 * @param resource|string $data
23
+	 * @return string
24
+	 */
25
+	public function put($data) {
26
+
27
+		file_put_contents($this->path, $data);
28
+		clearstatcache(true, $this->path);
29
+		return $this->getETag();
30
+
31
+	}
32
+
33
+	/**
34
+	 * Updates the file based on a range specification.
35
+	 *
36
+	 * The first argument is the data, which is either a readable stream
37
+	 * resource or a string.
38
+	 *
39
+	 * The second argument is the type of update we're doing.
40
+	 * This is either:
41
+	 * * 1. append
42
+	 * * 2. update based on a start byte
43
+	 * * 3. update based on an end byte
44
+	 *;
45
+	 * The third argument is the start or end byte.
46
+	 *
47
+	 * After a successful put operation, you may choose to return an ETag. The
48
+	 * ETAG must always be surrounded by double-quotes. These quotes must
49
+	 * appear in the actual string you're returning.
50
+	 *
51
+	 * Clients may use the ETag from a PUT request to later on make sure that
52
+	 * when they update the file, the contents haven't changed in the mean
53
+	 * time.
54
+	 *
55
+	 * @param resource|string $data
56
+	 * @param int $rangeType
57
+	 * @param int $offset
58
+	 * @return string|null
59
+	 */
60
+	public function patch($data, $rangeType, $offset = null) {
61
+
62
+		switch ($rangeType) {
63
+			case 1 :
64
+				$f = fopen($this->path, 'a');
65
+				break;
66
+			case 2 :
67
+				$f = fopen($this->path, 'c');
68
+				fseek($f, $offset);
69
+				break;
70
+			case 3 :
71
+				$f = fopen($this->path, 'c');
72
+				fseek($f, $offset, SEEK_END);
73
+				break;
74
+		}
75
+		if (is_string($data)) {
76
+			fwrite($f, $data);
77
+		} else {
78
+			stream_copy_to_stream($data, $f);
79
+		}
80
+		fclose($f);
81
+		clearstatcache(true, $this->path);
82
+		return $this->getETag();
83
+
84
+	}
85
+
86
+	/**
87
+	 * Returns the data
88
+	 *
89
+	 * @return resource
90
+	 */
91
+	public function get() {
92
+
93
+		return fopen($this->path, 'r');
94
+
95
+	}
96
+
97
+	/**
98
+	 * Delete the current file
99
+	 *
100
+	 * @return bool
101
+	 */
102
+	public function delete() {
103
+
104
+		return unlink($this->path);
105
+
106
+	}
107
+
108
+	/**
109
+	 * Returns the ETag for a file
110
+	 *
111
+	 * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change.
112
+	 * The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
113
+	 *
114
+	 * Return null if the ETag can not effectively be determined
115
+	 *
116
+	 * @return string|null
117
+	 */
118
+	public function getETag() {
119
+
120
+		return '"' . sha1(
121
+			fileinode($this->path) .
122
+			filesize($this->path) .
123
+			filemtime($this->path)
124
+		) . '"';
125
+
126
+	}
127
+
128
+	/**
129
+	 * Returns the mime-type for a file
130
+	 *
131
+	 * If null is returned, we'll assume application/octet-stream
132
+	 *
133
+	 * @return string|null
134
+	 */
135
+	public function getContentType() {
136
+
137
+		return null;
138
+
139
+	}
140
+
141
+	/**
142
+	 * Returns the size of the file, in bytes
143
+	 *
144
+	 * @return int
145
+	 */
146
+	public function getSize() {
147
+
148
+		return filesize($this->path);
149
+
150
+	}
151 151
 
152 152
 }
Please login to merge, or discard this patch.