Completed
Pull Request — master (#8833)
by Blizzz
40:18 queued 20:49
created
lib/private/Files/SimpleFS/SimpleFile.php 2 patches
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -26,7 +26,7 @@
 block discarded – undo
26 26
 use OCP\Files\NotPermittedException;
27 27
 use OCP\Files\SimpleFS\ISimpleFile;
28 28
 
29
-class SimpleFile implements ISimpleFile  {
29
+class SimpleFile implements ISimpleFile {
30 30
 
31 31
 	/** @var File $file */
32 32
 	private $file;
Please login to merge, or discard this patch.
Indentation   +117 added lines, -117 removed lines patch added patch discarded remove patch
@@ -29,121 +29,121 @@
 block discarded – undo
29 29
 
30 30
 class SimpleFile implements ISimpleFile  {
31 31
 
32
-	/** @var File $file */
33
-	private $file;
34
-
35
-	/**
36
-	 * File constructor.
37
-	 *
38
-	 * @param File $file
39
-	 */
40
-	public function __construct(File $file) {
41
-		$this->file = $file;
42
-	}
43
-
44
-	/**
45
-	 * Get the name
46
-	 *
47
-	 * @return string
48
-	 */
49
-	public function getName() {
50
-		return $this->file->getName();
51
-	}
52
-
53
-	/**
54
-	 * Get the size in bytes
55
-	 *
56
-	 * @return int
57
-	 */
58
-	public function getSize() {
59
-		return $this->file->getSize();
60
-	}
61
-
62
-	/**
63
-	 * Get the ETag
64
-	 *
65
-	 * @return string
66
-	 */
67
-	public function getETag() {
68
-		return $this->file->getEtag();
69
-	}
70
-
71
-	/**
72
-	 * Get the last modification time
73
-	 *
74
-	 * @return int
75
-	 */
76
-	public function getMTime() {
77
-		return $this->file->getMTime();
78
-	}
79
-
80
-	/**
81
-	 * Get the content
82
-	 *
83
-	 * @throws NotPermittedException
84
-	 * @throws NotFoundException
85
-	 * @return string
86
-	 */
87
-	public function getContent() {
88
-		$result = $this->file->getContent();
89
-
90
-		if ($result === false) {
91
-			$this->checkFile();
92
-		}
93
-
94
-		return $result;
95
-	}
96
-
97
-	/**
98
-	 * Overwrite the file
99
-	 *
100
-	 * @param string $data
101
-	 * @throws NotPermittedException
102
-	 */
103
-	public function putContent($data) {
104
-		$this->file->putContent($data);
105
-	}
106
-
107
-	/**
108
-	 * Sometimes there are some issues with the AppData. Most of them are from
109
-	 * user error. But we should handle them gracefull anyway.
110
-	 *
111
-	 * If for some reason the current file can't be found. We remove it.
112
-	 * Then traverse up and check all folders if they exists. This so that the
113
-	 * next request will have a valid appdata structure again.
114
-	 *
115
-	 * @throws NotFoundException
116
-	 */
117
-	private function checkFile() {
118
-		$cur = $this->file;
119
-
120
-		while ($cur->stat() === false) {
121
-			$parent = $cur->getParent();
122
-			$cur->delete();
123
-			$cur = $parent;
124
-		}
125
-
126
-		if ($cur !== $this->file) {
127
-			throw new NotFoundException('File does not exist');
128
-		}
129
-	}
130
-
131
-
132
-	/**
133
-	 * Delete the file
134
-	 *
135
-	 * @throws NotPermittedException
136
-	 */
137
-	public function delete() {
138
-		$this->file->delete();
139
-	}
140
-
141
-	/**
142
-	 * Get the MimeType
143
-	 *
144
-	 * @return string
145
-	 */
146
-	public function getMimeType() {
147
-		return $this->file->getMimeType();
148
-	}
32
+    /** @var File $file */
33
+    private $file;
34
+
35
+    /**
36
+     * File constructor.
37
+     *
38
+     * @param File $file
39
+     */
40
+    public function __construct(File $file) {
41
+        $this->file = $file;
42
+    }
43
+
44
+    /**
45
+     * Get the name
46
+     *
47
+     * @return string
48
+     */
49
+    public function getName() {
50
+        return $this->file->getName();
51
+    }
52
+
53
+    /**
54
+     * Get the size in bytes
55
+     *
56
+     * @return int
57
+     */
58
+    public function getSize() {
59
+        return $this->file->getSize();
60
+    }
61
+
62
+    /**
63
+     * Get the ETag
64
+     *
65
+     * @return string
66
+     */
67
+    public function getETag() {
68
+        return $this->file->getEtag();
69
+    }
70
+
71
+    /**
72
+     * Get the last modification time
73
+     *
74
+     * @return int
75
+     */
76
+    public function getMTime() {
77
+        return $this->file->getMTime();
78
+    }
79
+
80
+    /**
81
+     * Get the content
82
+     *
83
+     * @throws NotPermittedException
84
+     * @throws NotFoundException
85
+     * @return string
86
+     */
87
+    public function getContent() {
88
+        $result = $this->file->getContent();
89
+
90
+        if ($result === false) {
91
+            $this->checkFile();
92
+        }
93
+
94
+        return $result;
95
+    }
96
+
97
+    /**
98
+     * Overwrite the file
99
+     *
100
+     * @param string $data
101
+     * @throws NotPermittedException
102
+     */
103
+    public function putContent($data) {
104
+        $this->file->putContent($data);
105
+    }
106
+
107
+    /**
108
+     * Sometimes there are some issues with the AppData. Most of them are from
109
+     * user error. But we should handle them gracefull anyway.
110
+     *
111
+     * If for some reason the current file can't be found. We remove it.
112
+     * Then traverse up and check all folders if they exists. This so that the
113
+     * next request will have a valid appdata structure again.
114
+     *
115
+     * @throws NotFoundException
116
+     */
117
+    private function checkFile() {
118
+        $cur = $this->file;
119
+
120
+        while ($cur->stat() === false) {
121
+            $parent = $cur->getParent();
122
+            $cur->delete();
123
+            $cur = $parent;
124
+        }
125
+
126
+        if ($cur !== $this->file) {
127
+            throw new NotFoundException('File does not exist');
128
+        }
129
+    }
130
+
131
+
132
+    /**
133
+     * Delete the file
134
+     *
135
+     * @throws NotPermittedException
136
+     */
137
+    public function delete() {
138
+        $this->file->delete();
139
+    }
140
+
141
+    /**
142
+     * Get the MimeType
143
+     *
144
+     * @return string
145
+     */
146
+    public function getMimeType() {
147
+        return $this->file->getMimeType();
148
+    }
149 149
 }
Please login to merge, or discard this patch.
lib/private/Files/SimpleFS/SimpleFolder.php 2 patches
Indentation   +41 added lines, -41 removed lines patch added patch discarded remove patch
@@ -30,58 +30,58 @@
 block discarded – undo
30 30
 
31 31
 class SimpleFolder implements ISimpleFolder   {
32 32
 
33
-	/** @var Folder */
34
-	private $folder;
33
+    /** @var Folder */
34
+    private $folder;
35 35
 
36
-	/**
37
-	 * Folder constructor.
38
-	 *
39
-	 * @param Folder $folder
40
-	 */
41
-	public function __construct(Folder $folder) {
42
-		$this->folder = $folder;
43
-	}
36
+    /**
37
+     * Folder constructor.
38
+     *
39
+     * @param Folder $folder
40
+     */
41
+    public function __construct(Folder $folder) {
42
+        $this->folder = $folder;
43
+    }
44 44
 
45
-	public function getName() {
46
-		return $this->folder->getName();
47
-	}
45
+    public function getName() {
46
+        return $this->folder->getName();
47
+    }
48 48
 
49
-	public function getDirectoryListing() {
50
-		$listing = $this->folder->getDirectoryListing();
49
+    public function getDirectoryListing() {
50
+        $listing = $this->folder->getDirectoryListing();
51 51
 
52
-		$fileListing = array_map(function(Node $file) {
53
-			if ($file instanceof File) {
54
-				return new SimpleFile($file);
55
-			}
56
-			return null;
57
-		}, $listing);
52
+        $fileListing = array_map(function(Node $file) {
53
+            if ($file instanceof File) {
54
+                return new SimpleFile($file);
55
+            }
56
+            return null;
57
+        }, $listing);
58 58
 
59
-		$fileListing = array_filter($fileListing);
59
+        $fileListing = array_filter($fileListing);
60 60
 
61
-		return array_values($fileListing);
62
-	}
61
+        return array_values($fileListing);
62
+    }
63 63
 
64
-	public function delete() {
65
-		$this->folder->delete();
66
-	}
64
+    public function delete() {
65
+        $this->folder->delete();
66
+    }
67 67
 
68
-	public function fileExists($name) {
69
-		return $this->folder->nodeExists($name);
70
-	}
68
+    public function fileExists($name) {
69
+        return $this->folder->nodeExists($name);
70
+    }
71 71
 
72
-	public function getFile($name) {
73
-		$file = $this->folder->get($name);
72
+    public function getFile($name) {
73
+        $file = $this->folder->get($name);
74 74
 
75
-		if (!($file instanceof File)) {
76
-			throw new NotFoundException();
77
-		}
75
+        if (!($file instanceof File)) {
76
+            throw new NotFoundException();
77
+        }
78 78
 
79
-		return new SimpleFile($file);
80
-	}
79
+        return new SimpleFile($file);
80
+    }
81 81
 
82
-	public function newFile($name) {
83
-		$file = $this->folder->newFile($name);
82
+    public function newFile($name) {
83
+        $file = $this->folder->newFile($name);
84 84
 
85
-		return new SimpleFile($file);
86
-	}
85
+        return new SimpleFile($file);
86
+    }
87 87
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -28,7 +28,7 @@
 block discarded – undo
28 28
 use OCP\Files\NotFoundException;
29 29
 use OCP\Files\SimpleFS\ISimpleFolder;
30 30
 
31
-class SimpleFolder implements ISimpleFolder   {
31
+class SimpleFolder implements ISimpleFolder {
32 32
 
33 33
 	/** @var Folder */
34 34
 	private $folder;
Please login to merge, or discard this patch.
lib/private/Files/Storage/Wrapper/Encoding.php 2 patches
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -80,7 +80,7 @@  discard block
 block discarded – undo
80 80
 				// no point in continuing if the section was not found, use original path
81 81
 				return $fullPath;
82 82
 			}
83
-			$path = $convertedPath . '/';
83
+			$path = $convertedPath.'/';
84 84
 		}
85 85
 		$path = rtrim($path, '/');
86 86
 		return $path;
@@ -96,7 +96,7 @@  discard block
 block discarded – undo
96 96
 	 * @return string|null original or converted path, or null if none of the forms was found
97 97
 	 */
98 98
 	private function findPathToUseLastSection($basePath, $lastSection) {
99
-		$fullPath = $basePath . $lastSection;
99
+		$fullPath = $basePath.$lastSection;
100 100
 		if ($lastSection === '' || $this->isAscii($lastSection) || $this->storage->file_exists($fullPath)) {
101 101
 			$this->namesCache[$fullPath] = $fullPath;
102 102
 			return $fullPath;
@@ -108,7 +108,7 @@  discard block
 block discarded – undo
108 108
 		} else {
109 109
 			$otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_C);
110 110
 		}
111
-		$otherFullPath = $basePath . $otherFormPath;
111
+		$otherFullPath = $basePath.$otherFormPath;
112 112
 		if ($this->storage->file_exists($otherFullPath)) {
113 113
 			$this->namesCache[$fullPath] = $otherFullPath;
114 114
 			return $otherFullPath;
Please login to merge, or discard this patch.
Indentation   +498 added lines, -498 removed lines patch added patch discarded remove patch
@@ -34,502 +34,502 @@
 block discarded – undo
34 34
  */
35 35
 class Encoding extends Wrapper {
36 36
 
37
-	/**
38
-	 * @var ICache
39
-	 */
40
-	private $namesCache;
41
-
42
-	/**
43
-	 * @param array $parameters
44
-	 */
45
-	public function __construct($parameters) {
46
-		$this->storage = $parameters['storage'];
47
-		$this->namesCache = new CappedMemoryCache();
48
-	}
49
-
50
-	/**
51
-	 * Returns whether the given string is only made of ASCII characters
52
-	 *
53
-	 * @param string $str string
54
-	 *
55
-	 * @return bool true if the string is all ASCII, false otherwise
56
-	 */
57
-	private function isAscii($str) {
58
-		return (bool) !preg_match('/[\\x80-\\xff]+/', $str);
59
-	}
60
-
61
-	/**
62
-	 * Checks whether the given path exists in NFC or NFD form after checking
63
-	 * each form for each path section and returns the correct form.
64
-	 * If no existing path found, returns the path as it was given.
65
-	 *
66
-	 * @param string $fullPath path to check
67
-	 *
68
-	 * @return string original or converted path
69
-	 */
70
-	private function findPathToUse($fullPath) {
71
-		$cachedPath = $this->namesCache[$fullPath];
72
-		if ($cachedPath !== null) {
73
-			return $cachedPath;
74
-		}
75
-
76
-		$sections = explode('/', $fullPath);
77
-		$path = '';
78
-		foreach ($sections as $section) {
79
-			$convertedPath = $this->findPathToUseLastSection($path, $section);
80
-			if ($convertedPath === null) {
81
-				// no point in continuing if the section was not found, use original path
82
-				return $fullPath;
83
-			}
84
-			$path = $convertedPath . '/';
85
-		}
86
-		$path = rtrim($path, '/');
87
-		return $path;
88
-	}
89
-
90
-	/**
91
-	 * Checks whether the last path section of the given path exists in NFC or NFD form
92
-	 * and returns the correct form. If no existing path found, returns null.
93
-	 *
94
-	 * @param string $basePath base path to check
95
-	 * @param string $lastSection last section of the path to check for NFD/NFC variations
96
-	 *
97
-	 * @return string|null original or converted path, or null if none of the forms was found
98
-	 */
99
-	private function findPathToUseLastSection($basePath, $lastSection) {
100
-		$fullPath = $basePath . $lastSection;
101
-		if ($lastSection === '' || $this->isAscii($lastSection) || $this->storage->file_exists($fullPath)) {
102
-			$this->namesCache[$fullPath] = $fullPath;
103
-			return $fullPath;
104
-		}
105
-
106
-		// swap encoding
107
-		if (\Normalizer::isNormalized($lastSection, \Normalizer::FORM_C)) {
108
-			$otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_D);
109
-		} else {
110
-			$otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_C);
111
-		}
112
-		$otherFullPath = $basePath . $otherFormPath;
113
-		if ($this->storage->file_exists($otherFullPath)) {
114
-			$this->namesCache[$fullPath] = $otherFullPath;
115
-			return $otherFullPath;
116
-		}
117
-
118
-		// return original path, file did not exist at all
119
-		$this->namesCache[$fullPath] = $fullPath;
120
-		return null;
121
-	}
122
-
123
-	/**
124
-	 * see http://php.net/manual/en/function.mkdir.php
125
-	 *
126
-	 * @param string $path
127
-	 * @return bool
128
-	 */
129
-	public function mkdir($path) {
130
-		// note: no conversion here, method should not be called with non-NFC names!
131
-		$result = $this->storage->mkdir($path);
132
-		if ($result) {
133
-			$this->namesCache[$path] = $path;
134
-		}
135
-		return $result;
136
-	}
137
-
138
-	/**
139
-	 * see http://php.net/manual/en/function.rmdir.php
140
-	 *
141
-	 * @param string $path
142
-	 * @return bool
143
-	 */
144
-	public function rmdir($path) {
145
-		$result = $this->storage->rmdir($this->findPathToUse($path));
146
-		if ($result) {
147
-			unset($this->namesCache[$path]);
148
-		}
149
-		return $result;
150
-	}
151
-
152
-	/**
153
-	 * see http://php.net/manual/en/function.opendir.php
154
-	 *
155
-	 * @param string $path
156
-	 * @return resource
157
-	 */
158
-	public function opendir($path) {
159
-		return $this->storage->opendir($this->findPathToUse($path));
160
-	}
161
-
162
-	/**
163
-	 * see http://php.net/manual/en/function.is_dir.php
164
-	 *
165
-	 * @param string $path
166
-	 * @return bool
167
-	 */
168
-	public function is_dir($path) {
169
-		return $this->storage->is_dir($this->findPathToUse($path));
170
-	}
171
-
172
-	/**
173
-	 * see http://php.net/manual/en/function.is_file.php
174
-	 *
175
-	 * @param string $path
176
-	 * @return bool
177
-	 */
178
-	public function is_file($path) {
179
-		return $this->storage->is_file($this->findPathToUse($path));
180
-	}
181
-
182
-	/**
183
-	 * see http://php.net/manual/en/function.stat.php
184
-	 * only the following keys are required in the result: size and mtime
185
-	 *
186
-	 * @param string $path
187
-	 * @return array
188
-	 */
189
-	public function stat($path) {
190
-		return $this->storage->stat($this->findPathToUse($path));
191
-	}
192
-
193
-	/**
194
-	 * see http://php.net/manual/en/function.filetype.php
195
-	 *
196
-	 * @param string $path
197
-	 * @return bool
198
-	 */
199
-	public function filetype($path) {
200
-		return $this->storage->filetype($this->findPathToUse($path));
201
-	}
202
-
203
-	/**
204
-	 * see http://php.net/manual/en/function.filesize.php
205
-	 * The result for filesize when called on a folder is required to be 0
206
-	 *
207
-	 * @param string $path
208
-	 * @return int
209
-	 */
210
-	public function filesize($path) {
211
-		return $this->storage->filesize($this->findPathToUse($path));
212
-	}
213
-
214
-	/**
215
-	 * check if a file can be created in $path
216
-	 *
217
-	 * @param string $path
218
-	 * @return bool
219
-	 */
220
-	public function isCreatable($path) {
221
-		return $this->storage->isCreatable($this->findPathToUse($path));
222
-	}
223
-
224
-	/**
225
-	 * check if a file can be read
226
-	 *
227
-	 * @param string $path
228
-	 * @return bool
229
-	 */
230
-	public function isReadable($path) {
231
-		return $this->storage->isReadable($this->findPathToUse($path));
232
-	}
233
-
234
-	/**
235
-	 * check if a file can be written to
236
-	 *
237
-	 * @param string $path
238
-	 * @return bool
239
-	 */
240
-	public function isUpdatable($path) {
241
-		return $this->storage->isUpdatable($this->findPathToUse($path));
242
-	}
243
-
244
-	/**
245
-	 * check if a file can be deleted
246
-	 *
247
-	 * @param string $path
248
-	 * @return bool
249
-	 */
250
-	public function isDeletable($path) {
251
-		return $this->storage->isDeletable($this->findPathToUse($path));
252
-	}
253
-
254
-	/**
255
-	 * check if a file can be shared
256
-	 *
257
-	 * @param string $path
258
-	 * @return bool
259
-	 */
260
-	public function isSharable($path) {
261
-		return $this->storage->isSharable($this->findPathToUse($path));
262
-	}
263
-
264
-	/**
265
-	 * get the full permissions of a path.
266
-	 * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
267
-	 *
268
-	 * @param string $path
269
-	 * @return int
270
-	 */
271
-	public function getPermissions($path) {
272
-		return $this->storage->getPermissions($this->findPathToUse($path));
273
-	}
274
-
275
-	/**
276
-	 * see http://php.net/manual/en/function.file_exists.php
277
-	 *
278
-	 * @param string $path
279
-	 * @return bool
280
-	 */
281
-	public function file_exists($path) {
282
-		return $this->storage->file_exists($this->findPathToUse($path));
283
-	}
284
-
285
-	/**
286
-	 * see http://php.net/manual/en/function.filemtime.php
287
-	 *
288
-	 * @param string $path
289
-	 * @return int
290
-	 */
291
-	public function filemtime($path) {
292
-		return $this->storage->filemtime($this->findPathToUse($path));
293
-	}
294
-
295
-	/**
296
-	 * see http://php.net/manual/en/function.file_get_contents.php
297
-	 *
298
-	 * @param string $path
299
-	 * @return string
300
-	 */
301
-	public function file_get_contents($path) {
302
-		return $this->storage->file_get_contents($this->findPathToUse($path));
303
-	}
304
-
305
-	/**
306
-	 * see http://php.net/manual/en/function.file_put_contents.php
307
-	 *
308
-	 * @param string $path
309
-	 * @param string $data
310
-	 * @return bool
311
-	 */
312
-	public function file_put_contents($path, $data) {
313
-		return $this->storage->file_put_contents($this->findPathToUse($path), $data);
314
-	}
315
-
316
-	/**
317
-	 * see http://php.net/manual/en/function.unlink.php
318
-	 *
319
-	 * @param string $path
320
-	 * @return bool
321
-	 */
322
-	public function unlink($path) {
323
-		$result = $this->storage->unlink($this->findPathToUse($path));
324
-		if ($result) {
325
-			unset($this->namesCache[$path]);
326
-		}
327
-		return $result;
328
-	}
329
-
330
-	/**
331
-	 * see http://php.net/manual/en/function.rename.php
332
-	 *
333
-	 * @param string $path1
334
-	 * @param string $path2
335
-	 * @return bool
336
-	 */
337
-	public function rename($path1, $path2) {
338
-		// second name always NFC
339
-		return $this->storage->rename($this->findPathToUse($path1), $this->findPathToUse($path2));
340
-	}
341
-
342
-	/**
343
-	 * see http://php.net/manual/en/function.copy.php
344
-	 *
345
-	 * @param string $path1
346
-	 * @param string $path2
347
-	 * @return bool
348
-	 */
349
-	public function copy($path1, $path2) {
350
-		return $this->storage->copy($this->findPathToUse($path1), $this->findPathToUse($path2));
351
-	}
352
-
353
-	/**
354
-	 * see http://php.net/manual/en/function.fopen.php
355
-	 *
356
-	 * @param string $path
357
-	 * @param string $mode
358
-	 * @return resource
359
-	 */
360
-	public function fopen($path, $mode) {
361
-		$result = $this->storage->fopen($this->findPathToUse($path), $mode);
362
-		if ($result && $mode !== 'r' && $mode !== 'rb') {
363
-			unset($this->namesCache[$path]);
364
-		}
365
-		return $result;
366
-	}
367
-
368
-	/**
369
-	 * get the mimetype for a file or folder
370
-	 * The mimetype for a folder is required to be "httpd/unix-directory"
371
-	 *
372
-	 * @param string $path
373
-	 * @return string
374
-	 */
375
-	public function getMimeType($path) {
376
-		return $this->storage->getMimeType($this->findPathToUse($path));
377
-	}
378
-
379
-	/**
380
-	 * see http://php.net/manual/en/function.hash.php
381
-	 *
382
-	 * @param string $type
383
-	 * @param string $path
384
-	 * @param bool $raw
385
-	 * @return string
386
-	 */
387
-	public function hash($type, $path, $raw = false) {
388
-		return $this->storage->hash($type, $this->findPathToUse($path), $raw);
389
-	}
390
-
391
-	/**
392
-	 * see http://php.net/manual/en/function.free_space.php
393
-	 *
394
-	 * @param string $path
395
-	 * @return int
396
-	 */
397
-	public function free_space($path) {
398
-		return $this->storage->free_space($this->findPathToUse($path));
399
-	}
400
-
401
-	/**
402
-	 * search for occurrences of $query in file names
403
-	 *
404
-	 * @param string $query
405
-	 * @return array
406
-	 */
407
-	public function search($query) {
408
-		return $this->storage->search($query);
409
-	}
410
-
411
-	/**
412
-	 * see http://php.net/manual/en/function.touch.php
413
-	 * If the backend does not support the operation, false should be returned
414
-	 *
415
-	 * @param string $path
416
-	 * @param int $mtime
417
-	 * @return bool
418
-	 */
419
-	public function touch($path, $mtime = null) {
420
-		return $this->storage->touch($this->findPathToUse($path), $mtime);
421
-	}
422
-
423
-	/**
424
-	 * get the path to a local version of the file.
425
-	 * The local version of the file can be temporary and doesn't have to be persistent across requests
426
-	 *
427
-	 * @param string $path
428
-	 * @return string
429
-	 */
430
-	public function getLocalFile($path) {
431
-		return $this->storage->getLocalFile($this->findPathToUse($path));
432
-	}
433
-
434
-	/**
435
-	 * check if a file or folder has been updated since $time
436
-	 *
437
-	 * @param string $path
438
-	 * @param int $time
439
-	 * @return bool
440
-	 *
441
-	 * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
442
-	 * returning true for other changes in the folder is optional
443
-	 */
444
-	public function hasUpdated($path, $time) {
445
-		return $this->storage->hasUpdated($this->findPathToUse($path), $time);
446
-	}
447
-
448
-	/**
449
-	 * get a cache instance for the storage
450
-	 *
451
-	 * @param string $path
452
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
453
-	 * @return \OC\Files\Cache\Cache
454
-	 */
455
-	public function getCache($path = '', $storage = null) {
456
-		if (!$storage) {
457
-			$storage = $this;
458
-		}
459
-		return $this->storage->getCache($this->findPathToUse($path), $storage);
460
-	}
461
-
462
-	/**
463
-	 * get a scanner instance for the storage
464
-	 *
465
-	 * @param string $path
466
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
467
-	 * @return \OC\Files\Cache\Scanner
468
-	 */
469
-	public function getScanner($path = '', $storage = null) {
470
-		if (!$storage) {
471
-			$storage = $this;
472
-		}
473
-		return $this->storage->getScanner($this->findPathToUse($path), $storage);
474
-	}
475
-
476
-	/**
477
-	 * get the ETag for a file or folder
478
-	 *
479
-	 * @param string $path
480
-	 * @return string
481
-	 */
482
-	public function getETag($path) {
483
-		return $this->storage->getETag($this->findPathToUse($path));
484
-	}
485
-
486
-	/**
487
-	 * @param IStorage $sourceStorage
488
-	 * @param string $sourceInternalPath
489
-	 * @param string $targetInternalPath
490
-	 * @return bool
491
-	 */
492
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
493
-		if ($sourceStorage === $this) {
494
-			return $this->copy($sourceInternalPath, $this->findPathToUse($targetInternalPath));
495
-		}
496
-
497
-		$result = $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath));
498
-		if ($result) {
499
-			unset($this->namesCache[$targetInternalPath]);
500
-		}
501
-		return $result;
502
-	}
503
-
504
-	/**
505
-	 * @param IStorage $sourceStorage
506
-	 * @param string $sourceInternalPath
507
-	 * @param string $targetInternalPath
508
-	 * @return bool
509
-	 */
510
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
511
-		if ($sourceStorage === $this) {
512
-			$result = $this->rename($sourceInternalPath, $this->findPathToUse($targetInternalPath));
513
-			if ($result) {
514
-				unset($this->namesCache[$sourceInternalPath]);
515
-				unset($this->namesCache[$targetInternalPath]);
516
-			}
517
-			return $result;
518
-		}
519
-
520
-		$result = $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath));
521
-		if ($result) {
522
-			unset($this->namesCache[$sourceInternalPath]);
523
-			unset($this->namesCache[$targetInternalPath]);
524
-		}
525
-		return $result;
526
-	}
527
-
528
-	/**
529
-	 * @param string $path
530
-	 * @return array
531
-	 */
532
-	public function getMetaData($path) {
533
-		return $this->storage->getMetaData($this->findPathToUse($path));
534
-	}
37
+    /**
38
+     * @var ICache
39
+     */
40
+    private $namesCache;
41
+
42
+    /**
43
+     * @param array $parameters
44
+     */
45
+    public function __construct($parameters) {
46
+        $this->storage = $parameters['storage'];
47
+        $this->namesCache = new CappedMemoryCache();
48
+    }
49
+
50
+    /**
51
+     * Returns whether the given string is only made of ASCII characters
52
+     *
53
+     * @param string $str string
54
+     *
55
+     * @return bool true if the string is all ASCII, false otherwise
56
+     */
57
+    private function isAscii($str) {
58
+        return (bool) !preg_match('/[\\x80-\\xff]+/', $str);
59
+    }
60
+
61
+    /**
62
+     * Checks whether the given path exists in NFC or NFD form after checking
63
+     * each form for each path section and returns the correct form.
64
+     * If no existing path found, returns the path as it was given.
65
+     *
66
+     * @param string $fullPath path to check
67
+     *
68
+     * @return string original or converted path
69
+     */
70
+    private function findPathToUse($fullPath) {
71
+        $cachedPath = $this->namesCache[$fullPath];
72
+        if ($cachedPath !== null) {
73
+            return $cachedPath;
74
+        }
75
+
76
+        $sections = explode('/', $fullPath);
77
+        $path = '';
78
+        foreach ($sections as $section) {
79
+            $convertedPath = $this->findPathToUseLastSection($path, $section);
80
+            if ($convertedPath === null) {
81
+                // no point in continuing if the section was not found, use original path
82
+                return $fullPath;
83
+            }
84
+            $path = $convertedPath . '/';
85
+        }
86
+        $path = rtrim($path, '/');
87
+        return $path;
88
+    }
89
+
90
+    /**
91
+     * Checks whether the last path section of the given path exists in NFC or NFD form
92
+     * and returns the correct form. If no existing path found, returns null.
93
+     *
94
+     * @param string $basePath base path to check
95
+     * @param string $lastSection last section of the path to check for NFD/NFC variations
96
+     *
97
+     * @return string|null original or converted path, or null if none of the forms was found
98
+     */
99
+    private function findPathToUseLastSection($basePath, $lastSection) {
100
+        $fullPath = $basePath . $lastSection;
101
+        if ($lastSection === '' || $this->isAscii($lastSection) || $this->storage->file_exists($fullPath)) {
102
+            $this->namesCache[$fullPath] = $fullPath;
103
+            return $fullPath;
104
+        }
105
+
106
+        // swap encoding
107
+        if (\Normalizer::isNormalized($lastSection, \Normalizer::FORM_C)) {
108
+            $otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_D);
109
+        } else {
110
+            $otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_C);
111
+        }
112
+        $otherFullPath = $basePath . $otherFormPath;
113
+        if ($this->storage->file_exists($otherFullPath)) {
114
+            $this->namesCache[$fullPath] = $otherFullPath;
115
+            return $otherFullPath;
116
+        }
117
+
118
+        // return original path, file did not exist at all
119
+        $this->namesCache[$fullPath] = $fullPath;
120
+        return null;
121
+    }
122
+
123
+    /**
124
+     * see http://php.net/manual/en/function.mkdir.php
125
+     *
126
+     * @param string $path
127
+     * @return bool
128
+     */
129
+    public function mkdir($path) {
130
+        // note: no conversion here, method should not be called with non-NFC names!
131
+        $result = $this->storage->mkdir($path);
132
+        if ($result) {
133
+            $this->namesCache[$path] = $path;
134
+        }
135
+        return $result;
136
+    }
137
+
138
+    /**
139
+     * see http://php.net/manual/en/function.rmdir.php
140
+     *
141
+     * @param string $path
142
+     * @return bool
143
+     */
144
+    public function rmdir($path) {
145
+        $result = $this->storage->rmdir($this->findPathToUse($path));
146
+        if ($result) {
147
+            unset($this->namesCache[$path]);
148
+        }
149
+        return $result;
150
+    }
151
+
152
+    /**
153
+     * see http://php.net/manual/en/function.opendir.php
154
+     *
155
+     * @param string $path
156
+     * @return resource
157
+     */
158
+    public function opendir($path) {
159
+        return $this->storage->opendir($this->findPathToUse($path));
160
+    }
161
+
162
+    /**
163
+     * see http://php.net/manual/en/function.is_dir.php
164
+     *
165
+     * @param string $path
166
+     * @return bool
167
+     */
168
+    public function is_dir($path) {
169
+        return $this->storage->is_dir($this->findPathToUse($path));
170
+    }
171
+
172
+    /**
173
+     * see http://php.net/manual/en/function.is_file.php
174
+     *
175
+     * @param string $path
176
+     * @return bool
177
+     */
178
+    public function is_file($path) {
179
+        return $this->storage->is_file($this->findPathToUse($path));
180
+    }
181
+
182
+    /**
183
+     * see http://php.net/manual/en/function.stat.php
184
+     * only the following keys are required in the result: size and mtime
185
+     *
186
+     * @param string $path
187
+     * @return array
188
+     */
189
+    public function stat($path) {
190
+        return $this->storage->stat($this->findPathToUse($path));
191
+    }
192
+
193
+    /**
194
+     * see http://php.net/manual/en/function.filetype.php
195
+     *
196
+     * @param string $path
197
+     * @return bool
198
+     */
199
+    public function filetype($path) {
200
+        return $this->storage->filetype($this->findPathToUse($path));
201
+    }
202
+
203
+    /**
204
+     * see http://php.net/manual/en/function.filesize.php
205
+     * The result for filesize when called on a folder is required to be 0
206
+     *
207
+     * @param string $path
208
+     * @return int
209
+     */
210
+    public function filesize($path) {
211
+        return $this->storage->filesize($this->findPathToUse($path));
212
+    }
213
+
214
+    /**
215
+     * check if a file can be created in $path
216
+     *
217
+     * @param string $path
218
+     * @return bool
219
+     */
220
+    public function isCreatable($path) {
221
+        return $this->storage->isCreatable($this->findPathToUse($path));
222
+    }
223
+
224
+    /**
225
+     * check if a file can be read
226
+     *
227
+     * @param string $path
228
+     * @return bool
229
+     */
230
+    public function isReadable($path) {
231
+        return $this->storage->isReadable($this->findPathToUse($path));
232
+    }
233
+
234
+    /**
235
+     * check if a file can be written to
236
+     *
237
+     * @param string $path
238
+     * @return bool
239
+     */
240
+    public function isUpdatable($path) {
241
+        return $this->storage->isUpdatable($this->findPathToUse($path));
242
+    }
243
+
244
+    /**
245
+     * check if a file can be deleted
246
+     *
247
+     * @param string $path
248
+     * @return bool
249
+     */
250
+    public function isDeletable($path) {
251
+        return $this->storage->isDeletable($this->findPathToUse($path));
252
+    }
253
+
254
+    /**
255
+     * check if a file can be shared
256
+     *
257
+     * @param string $path
258
+     * @return bool
259
+     */
260
+    public function isSharable($path) {
261
+        return $this->storage->isSharable($this->findPathToUse($path));
262
+    }
263
+
264
+    /**
265
+     * get the full permissions of a path.
266
+     * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
267
+     *
268
+     * @param string $path
269
+     * @return int
270
+     */
271
+    public function getPermissions($path) {
272
+        return $this->storage->getPermissions($this->findPathToUse($path));
273
+    }
274
+
275
+    /**
276
+     * see http://php.net/manual/en/function.file_exists.php
277
+     *
278
+     * @param string $path
279
+     * @return bool
280
+     */
281
+    public function file_exists($path) {
282
+        return $this->storage->file_exists($this->findPathToUse($path));
283
+    }
284
+
285
+    /**
286
+     * see http://php.net/manual/en/function.filemtime.php
287
+     *
288
+     * @param string $path
289
+     * @return int
290
+     */
291
+    public function filemtime($path) {
292
+        return $this->storage->filemtime($this->findPathToUse($path));
293
+    }
294
+
295
+    /**
296
+     * see http://php.net/manual/en/function.file_get_contents.php
297
+     *
298
+     * @param string $path
299
+     * @return string
300
+     */
301
+    public function file_get_contents($path) {
302
+        return $this->storage->file_get_contents($this->findPathToUse($path));
303
+    }
304
+
305
+    /**
306
+     * see http://php.net/manual/en/function.file_put_contents.php
307
+     *
308
+     * @param string $path
309
+     * @param string $data
310
+     * @return bool
311
+     */
312
+    public function file_put_contents($path, $data) {
313
+        return $this->storage->file_put_contents($this->findPathToUse($path), $data);
314
+    }
315
+
316
+    /**
317
+     * see http://php.net/manual/en/function.unlink.php
318
+     *
319
+     * @param string $path
320
+     * @return bool
321
+     */
322
+    public function unlink($path) {
323
+        $result = $this->storage->unlink($this->findPathToUse($path));
324
+        if ($result) {
325
+            unset($this->namesCache[$path]);
326
+        }
327
+        return $result;
328
+    }
329
+
330
+    /**
331
+     * see http://php.net/manual/en/function.rename.php
332
+     *
333
+     * @param string $path1
334
+     * @param string $path2
335
+     * @return bool
336
+     */
337
+    public function rename($path1, $path2) {
338
+        // second name always NFC
339
+        return $this->storage->rename($this->findPathToUse($path1), $this->findPathToUse($path2));
340
+    }
341
+
342
+    /**
343
+     * see http://php.net/manual/en/function.copy.php
344
+     *
345
+     * @param string $path1
346
+     * @param string $path2
347
+     * @return bool
348
+     */
349
+    public function copy($path1, $path2) {
350
+        return $this->storage->copy($this->findPathToUse($path1), $this->findPathToUse($path2));
351
+    }
352
+
353
+    /**
354
+     * see http://php.net/manual/en/function.fopen.php
355
+     *
356
+     * @param string $path
357
+     * @param string $mode
358
+     * @return resource
359
+     */
360
+    public function fopen($path, $mode) {
361
+        $result = $this->storage->fopen($this->findPathToUse($path), $mode);
362
+        if ($result && $mode !== 'r' && $mode !== 'rb') {
363
+            unset($this->namesCache[$path]);
364
+        }
365
+        return $result;
366
+    }
367
+
368
+    /**
369
+     * get the mimetype for a file or folder
370
+     * The mimetype for a folder is required to be "httpd/unix-directory"
371
+     *
372
+     * @param string $path
373
+     * @return string
374
+     */
375
+    public function getMimeType($path) {
376
+        return $this->storage->getMimeType($this->findPathToUse($path));
377
+    }
378
+
379
+    /**
380
+     * see http://php.net/manual/en/function.hash.php
381
+     *
382
+     * @param string $type
383
+     * @param string $path
384
+     * @param bool $raw
385
+     * @return string
386
+     */
387
+    public function hash($type, $path, $raw = false) {
388
+        return $this->storage->hash($type, $this->findPathToUse($path), $raw);
389
+    }
390
+
391
+    /**
392
+     * see http://php.net/manual/en/function.free_space.php
393
+     *
394
+     * @param string $path
395
+     * @return int
396
+     */
397
+    public function free_space($path) {
398
+        return $this->storage->free_space($this->findPathToUse($path));
399
+    }
400
+
401
+    /**
402
+     * search for occurrences of $query in file names
403
+     *
404
+     * @param string $query
405
+     * @return array
406
+     */
407
+    public function search($query) {
408
+        return $this->storage->search($query);
409
+    }
410
+
411
+    /**
412
+     * see http://php.net/manual/en/function.touch.php
413
+     * If the backend does not support the operation, false should be returned
414
+     *
415
+     * @param string $path
416
+     * @param int $mtime
417
+     * @return bool
418
+     */
419
+    public function touch($path, $mtime = null) {
420
+        return $this->storage->touch($this->findPathToUse($path), $mtime);
421
+    }
422
+
423
+    /**
424
+     * get the path to a local version of the file.
425
+     * The local version of the file can be temporary and doesn't have to be persistent across requests
426
+     *
427
+     * @param string $path
428
+     * @return string
429
+     */
430
+    public function getLocalFile($path) {
431
+        return $this->storage->getLocalFile($this->findPathToUse($path));
432
+    }
433
+
434
+    /**
435
+     * check if a file or folder has been updated since $time
436
+     *
437
+     * @param string $path
438
+     * @param int $time
439
+     * @return bool
440
+     *
441
+     * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
442
+     * returning true for other changes in the folder is optional
443
+     */
444
+    public function hasUpdated($path, $time) {
445
+        return $this->storage->hasUpdated($this->findPathToUse($path), $time);
446
+    }
447
+
448
+    /**
449
+     * get a cache instance for the storage
450
+     *
451
+     * @param string $path
452
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
453
+     * @return \OC\Files\Cache\Cache
454
+     */
455
+    public function getCache($path = '', $storage = null) {
456
+        if (!$storage) {
457
+            $storage = $this;
458
+        }
459
+        return $this->storage->getCache($this->findPathToUse($path), $storage);
460
+    }
461
+
462
+    /**
463
+     * get a scanner instance for the storage
464
+     *
465
+     * @param string $path
466
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
467
+     * @return \OC\Files\Cache\Scanner
468
+     */
469
+    public function getScanner($path = '', $storage = null) {
470
+        if (!$storage) {
471
+            $storage = $this;
472
+        }
473
+        return $this->storage->getScanner($this->findPathToUse($path), $storage);
474
+    }
475
+
476
+    /**
477
+     * get the ETag for a file or folder
478
+     *
479
+     * @param string $path
480
+     * @return string
481
+     */
482
+    public function getETag($path) {
483
+        return $this->storage->getETag($this->findPathToUse($path));
484
+    }
485
+
486
+    /**
487
+     * @param IStorage $sourceStorage
488
+     * @param string $sourceInternalPath
489
+     * @param string $targetInternalPath
490
+     * @return bool
491
+     */
492
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
493
+        if ($sourceStorage === $this) {
494
+            return $this->copy($sourceInternalPath, $this->findPathToUse($targetInternalPath));
495
+        }
496
+
497
+        $result = $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath));
498
+        if ($result) {
499
+            unset($this->namesCache[$targetInternalPath]);
500
+        }
501
+        return $result;
502
+    }
503
+
504
+    /**
505
+     * @param IStorage $sourceStorage
506
+     * @param string $sourceInternalPath
507
+     * @param string $targetInternalPath
508
+     * @return bool
509
+     */
510
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
511
+        if ($sourceStorage === $this) {
512
+            $result = $this->rename($sourceInternalPath, $this->findPathToUse($targetInternalPath));
513
+            if ($result) {
514
+                unset($this->namesCache[$sourceInternalPath]);
515
+                unset($this->namesCache[$targetInternalPath]);
516
+            }
517
+            return $result;
518
+        }
519
+
520
+        $result = $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath));
521
+        if ($result) {
522
+            unset($this->namesCache[$sourceInternalPath]);
523
+            unset($this->namesCache[$targetInternalPath]);
524
+        }
525
+        return $result;
526
+    }
527
+
528
+    /**
529
+     * @param string $path
530
+     * @return array
531
+     */
532
+    public function getMetaData($path) {
533
+        return $this->storage->getMetaData($this->findPathToUse($path));
534
+    }
535 535
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/DAV.php 5 patches
Braces   +5 added lines, -2 removed lines patch added patch discarded remove patch
@@ -90,8 +90,11 @@
 block discarded – undo
90 90
 		if (isset($params['host']) && isset($params['user']) && isset($params['password'])) {
91 91
 			$host = $params['host'];
92 92
 			//remove leading http[s], will be generated in createBaseUri()
93
-			if (substr($host, 0, 8) == "https://") $host = substr($host, 8);
94
-			else if (substr($host, 0, 7) == "http://") $host = substr($host, 7);
93
+			if (substr($host, 0, 8) == "https://") {
94
+			    $host = substr($host, 8);
95
+			} else if (substr($host, 0, 7) == "http://") {
96
+			    $host = substr($host, 7);
97
+			}
95 98
 			$this->host = $host;
96 99
 			$this->user = $params['user'];
97 100
 			$this->password = $params['password'];
Please login to merge, or discard this patch.
Doc Comments   +3 added lines patch added patch discarded remove patch
@@ -598,6 +598,9 @@
 block discarded – undo
598 598
 		}
599 599
 	}
600 600
 
601
+	/**
602
+	 * @param string $path
603
+	 */
601 604
 	public function getMimeTypeFromRemote($path) {
602 605
 		try {
603 606
 			$response = $this->propfind($path);
Please login to merge, or discard this patch.
Spacing   +34 added lines, -34 removed lines patch added patch discarded remove patch
@@ -102,7 +102,7 @@  discard block
 block discarded – undo
102 102
 				if (is_string($params['secure'])) {
103 103
 					$this->secure = ($params['secure'] === 'true');
104 104
 				} else {
105
-					$this->secure = (bool)$params['secure'];
105
+					$this->secure = (bool) $params['secure'];
106 106
 				}
107 107
 			} else {
108 108
 				$this->secure = false;
@@ -119,8 +119,8 @@  discard block
 block discarded – undo
119 119
 				}
120 120
 			}
121 121
 			$this->root = $params['root'] ?? '/';
122
-			$this->root = '/' . ltrim($this->root, '/');
123
-			$this->root = rtrim($this->root, '/') . '/';
122
+			$this->root = '/'.ltrim($this->root, '/');
123
+			$this->root = rtrim($this->root, '/').'/';
124 124
 		} else {
125 125
 			throw new \Exception('Invalid webdav storage configuration');
126 126
 		}
@@ -162,7 +162,7 @@  discard block
 block discarded – undo
162 162
 
163 163
 	/** {@inheritdoc} */
164 164
 	public function getId() {
165
-		return 'webdav::' . $this->user . '@' . $this->host . '/' . $this->root;
165
+		return 'webdav::'.$this->user.'@'.$this->host.'/'.$this->root;
166 166
 	}
167 167
 
168 168
 	/** {@inheritdoc} */
@@ -171,7 +171,7 @@  discard block
 block discarded – undo
171 171
 		if ($this->secure) {
172 172
 			$baseUri .= 's';
173 173
 		}
174
-		$baseUri .= '://' . $this->host . $this->root;
174
+		$baseUri .= '://'.$this->host.$this->root;
175 175
 		return $baseUri;
176 176
 	}
177 177
 
@@ -192,8 +192,8 @@  discard block
 block discarded – undo
192 192
 		$path = $this->cleanPath($path);
193 193
 		// FIXME: some WebDAV impl return 403 when trying to DELETE
194 194
 		// a non-empty folder
195
-		$result = $this->simpleResponse('DELETE', $path . '/', null, 204);
196
-		$this->statCache->clear($path . '/');
195
+		$result = $this->simpleResponse('DELETE', $path.'/', null, 204);
196
+		$this->statCache->clear($path.'/');
197 197
 		$this->statCache->remove($path);
198 198
 		return $result;
199 199
 	}
@@ -268,7 +268,7 @@  discard block
 block discarded – undo
268 268
 				$this->statCache->set($path, $response);
269 269
 			} catch (ClientHttpException $e) {
270 270
 				if ($e->getHttpStatus() === 404) {
271
-					$this->statCache->clear($path . '/');
271
+					$this->statCache->clear($path.'/');
272 272
 					$this->statCache->set($path, false);
273 273
 					return false;
274 274
 				}
@@ -325,7 +325,7 @@  discard block
 block discarded – undo
325 325
 		$this->init();
326 326
 		$path = $this->cleanPath($path);
327 327
 		$result = $this->simpleResponse('DELETE', $path, null, 204);
328
-		$this->statCache->clear($path . '/');
328
+		$this->statCache->clear($path.'/');
329 329
 		$this->statCache->remove($path);
330 330
 		return $result;
331 331
 	}
@@ -340,7 +340,7 @@  discard block
 block discarded – undo
340 340
 				try {
341 341
 					$response = $this->httpClientService
342 342
 						->newClient()
343
-						->get($this->createBaseUri() . $this->encodePath($path), [
343
+						->get($this->createBaseUri().$this->encodePath($path), [
344 344
 							'auth' => [$this->user, $this->password],
345 345
 							'stream' => true
346 346
 						]);
@@ -357,7 +357,7 @@  discard block
 block discarded – undo
357 357
 					if ($response->getStatusCode() === Http::STATUS_LOCKED) {
358 358
 						throw new \OCP\Lock\LockedException($path);
359 359
 					} else {
360
-						Util::writeLog("webdav client", 'Guzzle get returned status code ' . $response->getStatusCode(), Util::ERROR);
360
+						Util::writeLog("webdav client", 'Guzzle get returned status code '.$response->getStatusCode(), Util::ERROR);
361 361
 					}
362 362
 				}
363 363
 
@@ -397,7 +397,7 @@  discard block
 block discarded – undo
397 397
 					$tmpFile = $tempManager->getTemporaryFile($ext);
398 398
 				}
399 399
 				$handle = fopen($tmpFile, $mode);
400
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
400
+				return CallbackWrapper::wrap($handle, null, null, function() use ($path, $tmpFile) {
401 401
 					$this->writeBack($tmpFile, $path);
402 402
 				});
403 403
 		}
@@ -422,7 +422,7 @@  discard block
 block discarded – undo
422 422
 				return FileInfo::SPACE_UNKNOWN;
423 423
 			}
424 424
 			if (isset($response['{DAV:}quota-available-bytes'])) {
425
-				return (int)$response['{DAV:}quota-available-bytes'];
425
+				return (int) $response['{DAV:}quota-available-bytes'];
426 426
 			} else {
427 427
 				return FileInfo::SPACE_UNKNOWN;
428 428
 			}
@@ -498,7 +498,7 @@  discard block
 block discarded – undo
498 498
 
499 499
 		$this->httpClientService
500 500
 			->newClient()
501
-			->put($this->createBaseUri() . $this->encodePath($target), [
501
+			->put($this->createBaseUri().$this->encodePath($target), [
502 502
 				'body' => $source,
503 503
 				'auth' => [$this->user, $this->password]
504 504
 			]);
@@ -515,18 +515,18 @@  discard block
 block discarded – undo
515 515
 			// overwrite directory ?
516 516
 			if ($this->is_dir($path2)) {
517 517
 				// needs trailing slash in destination
518
-				$path2 = rtrim($path2, '/') . '/';
518
+				$path2 = rtrim($path2, '/').'/';
519 519
 			}
520 520
 			$this->client->request(
521 521
 				'MOVE',
522 522
 				$this->encodePath($path1),
523 523
 				null,
524 524
 				[
525
-					'Destination' => $this->createBaseUri() . $this->encodePath($path2),
525
+					'Destination' => $this->createBaseUri().$this->encodePath($path2),
526 526
 				]
527 527
 			);
528
-			$this->statCache->clear($path1 . '/');
529
-			$this->statCache->clear($path2 . '/');
528
+			$this->statCache->clear($path1.'/');
529
+			$this->statCache->clear($path2.'/');
530 530
 			$this->statCache->set($path1, false);
531 531
 			$this->statCache->set($path2, true);
532 532
 			$this->removeCachedFile($path1);
@@ -547,17 +547,17 @@  discard block
 block discarded – undo
547 547
 			// overwrite directory ?
548 548
 			if ($this->is_dir($path2)) {
549 549
 				// needs trailing slash in destination
550
-				$path2 = rtrim($path2, '/') . '/';
550
+				$path2 = rtrim($path2, '/').'/';
551 551
 			}
552 552
 			$this->client->request(
553 553
 				'COPY',
554 554
 				$this->encodePath($path1),
555 555
 				null,
556 556
 				[
557
-					'Destination' => $this->createBaseUri() . $this->encodePath($path2),
557
+					'Destination' => $this->createBaseUri().$this->encodePath($path2),
558 558
 				]
559 559
 			);
560
-			$this->statCache->clear($path2 . '/');
560
+			$this->statCache->clear($path2.'/');
561 561
 			$this->statCache->set($path2, true);
562 562
 			$this->removeCachedFile($path2);
563 563
 			return true;
@@ -576,7 +576,7 @@  discard block
 block discarded – undo
576 576
 			}
577 577
 			return [
578 578
 				'mtime' => strtotime($response['{DAV:}getlastmodified']),
579
-				'size' => (int)isset($response['{DAV:}getcontentlength']) ? $response['{DAV:}getcontentlength'] : 0,
579
+				'size' => (int) isset($response['{DAV:}getcontentlength']) ? $response['{DAV:}getcontentlength'] : 0,
580 580
 			];
581 581
 		} catch (\Exception $e) {
582 582
 			$this->convertException($e, $path);
@@ -658,7 +658,7 @@  discard block
 block discarded – undo
658 658
 			return $response['statusCode'] == $expected;
659 659
 		} catch (ClientHttpException $e) {
660 660
 			if ($e->getHttpStatus() === 404 && $method === 'DELETE') {
661
-				$this->statCache->clear($path . '/');
661
+				$this->statCache->clear($path.'/');
662 662
 				$this->statCache->set($path, false);
663 663
 				return false;
664 664
 			}
@@ -679,22 +679,22 @@  discard block
 block discarded – undo
679 679
 
680 680
 	/** {@inheritdoc} */
681 681
 	public function isUpdatable($path) {
682
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_UPDATE);
682
+		return (bool) ($this->getPermissions($path) & Constants::PERMISSION_UPDATE);
683 683
 	}
684 684
 
685 685
 	/** {@inheritdoc} */
686 686
 	public function isCreatable($path) {
687
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_CREATE);
687
+		return (bool) ($this->getPermissions($path) & Constants::PERMISSION_CREATE);
688 688
 	}
689 689
 
690 690
 	/** {@inheritdoc} */
691 691
 	public function isSharable($path) {
692
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE);
692
+		return (bool) ($this->getPermissions($path) & Constants::PERMISSION_SHARE);
693 693
 	}
694 694
 
695 695
 	/** {@inheritdoc} */
696 696
 	public function isDeletable($path) {
697
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_DELETE);
697
+		return (bool) ($this->getPermissions($path) & Constants::PERMISSION_DELETE);
698 698
 	}
699 699
 
700 700
 	/** {@inheritdoc} */
@@ -770,7 +770,7 @@  discard block
 block discarded – undo
770 770
 			if ($response === false) {
771 771
 				if ($path === '') {
772 772
 					// if root is gone it means the storage is not available
773
-					throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
773
+					throw new StorageNotAvailableException(get_class($e).': '.$e->getMessage());
774 774
 				}
775 775
 				return false;
776 776
 			}
@@ -783,7 +783,7 @@  discard block
 block discarded – undo
783 783
 				if (!empty($etag) && $cachedData['etag'] !== $etag) {
784 784
 					return true;
785 785
 				} else if (isset($response['{http://open-collaboration-services.org/ns}share-permissions'])) {
786
-					$sharePermissions = (int)$response['{http://open-collaboration-services.org/ns}share-permissions'];
786
+					$sharePermissions = (int) $response['{http://open-collaboration-services.org/ns}share-permissions'];
787 787
 					return $sharePermissions !== $cachedData['permissions'];
788 788
 				} else if (isset($response['{http://owncloud.org/ns}permissions'])) {
789 789
 					$permissions = $this->parsePermissions($response['{http://owncloud.org/ns}permissions']);
@@ -799,7 +799,7 @@  discard block
 block discarded – undo
799 799
 			if ($e->getHttpStatus() === 405) {
800 800
 				if ($path === '') {
801 801
 					// if root is gone it means the storage is not available
802
-					throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
802
+					throw new StorageNotAvailableException(get_class($e).': '.$e->getMessage());
803 803
 				}
804 804
 				return false;
805 805
 			}
@@ -833,19 +833,19 @@  discard block
 block discarded – undo
833 833
 			}
834 834
 			if ($e->getHttpStatus() === Http::STATUS_UNAUTHORIZED) {
835 835
 				// either password was changed or was invalid all along
836
-				throw new StorageInvalidException(get_class($e) . ': ' . $e->getMessage());
836
+				throw new StorageInvalidException(get_class($e).': '.$e->getMessage());
837 837
 			} else if ($e->getHttpStatus() === Http::STATUS_METHOD_NOT_ALLOWED) {
838 838
 				// ignore exception for MethodNotAllowed, false will be returned
839 839
 				return;
840 840
 			}
841
-			throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
841
+			throw new StorageNotAvailableException(get_class($e).': '.$e->getMessage());
842 842
 		} else if ($e instanceof ClientException) {
843 843
 			// connection timeout or refused, server could be temporarily down
844
-			throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
844
+			throw new StorageNotAvailableException(get_class($e).': '.$e->getMessage());
845 845
 		} else if ($e instanceof \InvalidArgumentException) {
846 846
 			// parse error because the server returned HTML instead of XML,
847 847
 			// possibly temporarily down
848
-			throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
848
+			throw new StorageNotAvailableException(get_class($e).': '.$e->getMessage());
849 849
 		} else if (($e instanceof StorageNotAvailableException) || ($e instanceof StorageInvalidException)) {
850 850
 			// rethrow
851 851
 			throw $e;
Please login to merge, or discard this patch.
Unused Use Statements   -1 removed lines patch added patch discarded remove patch
@@ -34,7 +34,6 @@
 block discarded – undo
34 34
 namespace OC\Files\Storage;
35 35
 
36 36
 use Exception;
37
-use GuzzleHttp\Exception\RequestException;
38 37
 use Psr\Http\Message\ResponseInterface;
39 38
 use Icewind\Streams\CallbackWrapper;
40 39
 use OC\Files\Filesystem;
Please login to merge, or discard this patch.
Indentation   +796 added lines, -796 removed lines patch added patch discarded remove patch
@@ -57,801 +57,801 @@
 block discarded – undo
57 57
  * @package OC\Files\Storage
58 58
  */
59 59
 class DAV extends Common {
60
-	/** @var string */
61
-	protected $password;
62
-	/** @var string */
63
-	protected $user;
64
-	/** @var string */
65
-	protected $authType;
66
-	/** @var string */
67
-	protected $host;
68
-	/** @var bool */
69
-	protected $secure;
70
-	/** @var string */
71
-	protected $root;
72
-	/** @var string */
73
-	protected $certPath;
74
-	/** @var bool */
75
-	protected $ready;
76
-	/** @var Client */
77
-	protected $client;
78
-	/** @var ArrayCache */
79
-	protected $statCache;
80
-	/** @var \OCP\Http\Client\IClientService */
81
-	protected $httpClientService;
82
-
83
-	/**
84
-	 * @param array $params
85
-	 * @throws \Exception
86
-	 */
87
-	public function __construct($params) {
88
-		$this->statCache = new ArrayCache();
89
-		$this->httpClientService = \OC::$server->getHTTPClientService();
90
-		if (isset($params['host']) && isset($params['user']) && isset($params['password'])) {
91
-			$host = $params['host'];
92
-			//remove leading http[s], will be generated in createBaseUri()
93
-			if (substr($host, 0, 8) == "https://") $host = substr($host, 8);
94
-			else if (substr($host, 0, 7) == "http://") $host = substr($host, 7);
95
-			$this->host = $host;
96
-			$this->user = $params['user'];
97
-			$this->password = $params['password'];
98
-			if (isset($params['authType'])) {
99
-				$this->authType = $params['authType'];
100
-			}
101
-			if (isset($params['secure'])) {
102
-				if (is_string($params['secure'])) {
103
-					$this->secure = ($params['secure'] === 'true');
104
-				} else {
105
-					$this->secure = (bool)$params['secure'];
106
-				}
107
-			} else {
108
-				$this->secure = false;
109
-			}
110
-			if ($this->secure === true) {
111
-				// inject mock for testing
112
-				$certManager = \OC::$server->getCertificateManager();
113
-				if (is_null($certManager)) { //no user
114
-					$certManager = \OC::$server->getCertificateManager(null);
115
-				}
116
-				$certPath = $certManager->getAbsoluteBundlePath();
117
-				if (file_exists($certPath)) {
118
-					$this->certPath = $certPath;
119
-				}
120
-			}
121
-			$this->root = $params['root'] ?? '/';
122
-			$this->root = '/' . ltrim($this->root, '/');
123
-			$this->root = rtrim($this->root, '/') . '/';
124
-		} else {
125
-			throw new \Exception('Invalid webdav storage configuration');
126
-		}
127
-	}
128
-
129
-	protected function init() {
130
-		if ($this->ready) {
131
-			return;
132
-		}
133
-		$this->ready = true;
134
-
135
-		$settings = [
136
-			'baseUri' => $this->createBaseUri(),
137
-			'userName' => $this->user,
138
-			'password' => $this->password,
139
-		];
140
-		if (isset($this->authType)) {
141
-			$settings['authType'] = $this->authType;
142
-		}
143
-
144
-		$proxy = \OC::$server->getConfig()->getSystemValue('proxy', '');
145
-		if ($proxy !== '') {
146
-			$settings['proxy'] = $proxy;
147
-		}
148
-
149
-		$this->client = new Client($settings);
150
-		$this->client->setThrowExceptions(true);
151
-		if ($this->secure === true && $this->certPath) {
152
-			$this->client->addCurlSetting(CURLOPT_CAINFO, $this->certPath);
153
-		}
154
-	}
155
-
156
-	/**
157
-	 * Clear the stat cache
158
-	 */
159
-	public function clearStatCache() {
160
-		$this->statCache->clear();
161
-	}
162
-
163
-	/** {@inheritdoc} */
164
-	public function getId() {
165
-		return 'webdav::' . $this->user . '@' . $this->host . '/' . $this->root;
166
-	}
167
-
168
-	/** {@inheritdoc} */
169
-	public function createBaseUri() {
170
-		$baseUri = 'http';
171
-		if ($this->secure) {
172
-			$baseUri .= 's';
173
-		}
174
-		$baseUri .= '://' . $this->host . $this->root;
175
-		return $baseUri;
176
-	}
177
-
178
-	/** {@inheritdoc} */
179
-	public function mkdir($path) {
180
-		$this->init();
181
-		$path = $this->cleanPath($path);
182
-		$result = $this->simpleResponse('MKCOL', $path, null, 201);
183
-		if ($result) {
184
-			$this->statCache->set($path, true);
185
-		}
186
-		return $result;
187
-	}
188
-
189
-	/** {@inheritdoc} */
190
-	public function rmdir($path) {
191
-		$this->init();
192
-		$path = $this->cleanPath($path);
193
-		// FIXME: some WebDAV impl return 403 when trying to DELETE
194
-		// a non-empty folder
195
-		$result = $this->simpleResponse('DELETE', $path . '/', null, 204);
196
-		$this->statCache->clear($path . '/');
197
-		$this->statCache->remove($path);
198
-		return $result;
199
-	}
200
-
201
-	/** {@inheritdoc} */
202
-	public function opendir($path) {
203
-		$this->init();
204
-		$path = $this->cleanPath($path);
205
-		try {
206
-			$response = $this->client->propFind(
207
-				$this->encodePath($path),
208
-				['{DAV:}href'],
209
-				1
210
-			);
211
-			if ($response === false) {
212
-				return false;
213
-			}
214
-			$content = [];
215
-			$files = array_keys($response);
216
-			array_shift($files); //the first entry is the current directory
217
-
218
-			if (!$this->statCache->hasKey($path)) {
219
-				$this->statCache->set($path, true);
220
-			}
221
-			foreach ($files as $file) {
222
-				$file = urldecode($file);
223
-				// do not store the real entry, we might not have all properties
224
-				if (!$this->statCache->hasKey($path)) {
225
-					$this->statCache->set($file, true);
226
-				}
227
-				$file = basename($file);
228
-				$content[] = $file;
229
-			}
230
-			return IteratorDirectory::wrap($content);
231
-		} catch (\Exception $e) {
232
-			$this->convertException($e, $path);
233
-		}
234
-		return false;
235
-	}
236
-
237
-	/**
238
-	 * Propfind call with cache handling.
239
-	 *
240
-	 * First checks if information is cached.
241
-	 * If not, request it from the server then store to cache.
242
-	 *
243
-	 * @param string $path path to propfind
244
-	 *
245
-	 * @return array|boolean propfind response or false if the entry was not found
246
-	 *
247
-	 * @throws ClientHttpException
248
-	 */
249
-	protected function propfind($path) {
250
-		$path = $this->cleanPath($path);
251
-		$cachedResponse = $this->statCache->get($path);
252
-		// we either don't know it, or we know it exists but need more details
253
-		if (is_null($cachedResponse) || $cachedResponse === true) {
254
-			$this->init();
255
-			try {
256
-				$response = $this->client->propFind(
257
-					$this->encodePath($path),
258
-					array(
259
-						'{DAV:}getlastmodified',
260
-						'{DAV:}getcontentlength',
261
-						'{DAV:}getcontenttype',
262
-						'{http://owncloud.org/ns}permissions',
263
-						'{http://open-collaboration-services.org/ns}share-permissions',
264
-						'{DAV:}resourcetype',
265
-						'{DAV:}getetag',
266
-					)
267
-				);
268
-				$this->statCache->set($path, $response);
269
-			} catch (ClientHttpException $e) {
270
-				if ($e->getHttpStatus() === 404) {
271
-					$this->statCache->clear($path . '/');
272
-					$this->statCache->set($path, false);
273
-					return false;
274
-				}
275
-				$this->convertException($e, $path);
276
-			} catch (\Exception $e) {
277
-				$this->convertException($e, $path);
278
-			}
279
-		} else {
280
-			$response = $cachedResponse;
281
-		}
282
-		return $response;
283
-	}
284
-
285
-	/** {@inheritdoc} */
286
-	public function filetype($path) {
287
-		try {
288
-			$response = $this->propfind($path);
289
-			if ($response === false) {
290
-				return false;
291
-			}
292
-			$responseType = [];
293
-			if (isset($response["{DAV:}resourcetype"])) {
294
-				/** @var ResourceType[] $response */
295
-				$responseType = $response["{DAV:}resourcetype"]->getValue();
296
-			}
297
-			return (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file';
298
-		} catch (\Exception $e) {
299
-			$this->convertException($e, $path);
300
-		}
301
-		return false;
302
-	}
303
-
304
-	/** {@inheritdoc} */
305
-	public function file_exists($path) {
306
-		try {
307
-			$path = $this->cleanPath($path);
308
-			$cachedState = $this->statCache->get($path);
309
-			if ($cachedState === false) {
310
-				// we know the file doesn't exist
311
-				return false;
312
-			} else if (!is_null($cachedState)) {
313
-				return true;
314
-			}
315
-			// need to get from server
316
-			return ($this->propfind($path) !== false);
317
-		} catch (\Exception $e) {
318
-			$this->convertException($e, $path);
319
-		}
320
-		return false;
321
-	}
322
-
323
-	/** {@inheritdoc} */
324
-	public function unlink($path) {
325
-		$this->init();
326
-		$path = $this->cleanPath($path);
327
-		$result = $this->simpleResponse('DELETE', $path, null, 204);
328
-		$this->statCache->clear($path . '/');
329
-		$this->statCache->remove($path);
330
-		return $result;
331
-	}
332
-
333
-	/** {@inheritdoc} */
334
-	public function fopen($path, $mode) {
335
-		$this->init();
336
-		$path = $this->cleanPath($path);
337
-		switch ($mode) {
338
-			case 'r':
339
-			case 'rb':
340
-				try {
341
-					$response = $this->httpClientService
342
-						->newClient()
343
-						->get($this->createBaseUri() . $this->encodePath($path), [
344
-							'auth' => [$this->user, $this->password],
345
-							'stream' => true
346
-						]);
347
-				} catch (\GuzzleHttp\Exception\ClientException $e) {
348
-					if ($e->getResponse() instanceof ResponseInterface
349
-						&& $e->getResponse()->getStatusCode() === 404) {
350
-						return false;
351
-					} else {
352
-						throw $e;
353
-					}
354
-				}
355
-
356
-				if ($response->getStatusCode() !== Http::STATUS_OK) {
357
-					if ($response->getStatusCode() === Http::STATUS_LOCKED) {
358
-						throw new \OCP\Lock\LockedException($path);
359
-					} else {
360
-						Util::writeLog("webdav client", 'Guzzle get returned status code ' . $response->getStatusCode(), Util::ERROR);
361
-					}
362
-				}
363
-
364
-				return $response->getBody();
365
-			case 'w':
366
-			case 'wb':
367
-			case 'a':
368
-			case 'ab':
369
-			case 'r+':
370
-			case 'w+':
371
-			case 'wb+':
372
-			case 'a+':
373
-			case 'x':
374
-			case 'x+':
375
-			case 'c':
376
-			case 'c+':
377
-				//emulate these
378
-				$tempManager = \OC::$server->getTempManager();
379
-				if (strrpos($path, '.') !== false) {
380
-					$ext = substr($path, strrpos($path, '.'));
381
-				} else {
382
-					$ext = '';
383
-				}
384
-				if ($this->file_exists($path)) {
385
-					if (!$this->isUpdatable($path)) {
386
-						return false;
387
-					}
388
-					if ($mode === 'w' or $mode === 'w+') {
389
-						$tmpFile = $tempManager->getTemporaryFile($ext);
390
-					} else {
391
-						$tmpFile = $this->getCachedFile($path);
392
-					}
393
-				} else {
394
-					if (!$this->isCreatable(dirname($path))) {
395
-						return false;
396
-					}
397
-					$tmpFile = $tempManager->getTemporaryFile($ext);
398
-				}
399
-				$handle = fopen($tmpFile, $mode);
400
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
401
-					$this->writeBack($tmpFile, $path);
402
-				});
403
-		}
404
-	}
405
-
406
-	/**
407
-	 * @param string $tmpFile
408
-	 */
409
-	public function writeBack($tmpFile, $path) {
410
-		$this->uploadFile($tmpFile, $path);
411
-		unlink($tmpFile);
412
-	}
413
-
414
-	/** {@inheritdoc} */
415
-	public function free_space($path) {
416
-		$this->init();
417
-		$path = $this->cleanPath($path);
418
-		try {
419
-			// TODO: cacheable ?
420
-			$response = $this->client->propfind($this->encodePath($path), ['{DAV:}quota-available-bytes']);
421
-			if ($response === false) {
422
-				return FileInfo::SPACE_UNKNOWN;
423
-			}
424
-			if (isset($response['{DAV:}quota-available-bytes'])) {
425
-				return (int)$response['{DAV:}quota-available-bytes'];
426
-			} else {
427
-				return FileInfo::SPACE_UNKNOWN;
428
-			}
429
-		} catch (\Exception $e) {
430
-			return FileInfo::SPACE_UNKNOWN;
431
-		}
432
-	}
433
-
434
-	/** {@inheritdoc} */
435
-	public function touch($path, $mtime = null) {
436
-		$this->init();
437
-		if (is_null($mtime)) {
438
-			$mtime = time();
439
-		}
440
-		$path = $this->cleanPath($path);
441
-
442
-		// if file exists, update the mtime, else create a new empty file
443
-		if ($this->file_exists($path)) {
444
-			try {
445
-				$this->statCache->remove($path);
446
-				$this->client->proppatch($this->encodePath($path), ['{DAV:}lastmodified' => $mtime]);
447
-				// non-owncloud clients might not have accepted the property, need to recheck it
448
-				$response = $this->client->propfind($this->encodePath($path), ['{DAV:}getlastmodified'], 0);
449
-				if ($response === false) {
450
-					return false;
451
-				}
452
-				if (isset($response['{DAV:}getlastmodified'])) {
453
-					$remoteMtime = strtotime($response['{DAV:}getlastmodified']);
454
-					if ($remoteMtime !== $mtime) {
455
-						// server has not accepted the mtime
456
-						return false;
457
-					}
458
-				}
459
-			} catch (ClientHttpException $e) {
460
-				if ($e->getHttpStatus() === 501) {
461
-					return false;
462
-				}
463
-				$this->convertException($e, $path);
464
-				return false;
465
-			} catch (\Exception $e) {
466
-				$this->convertException($e, $path);
467
-				return false;
468
-			}
469
-		} else {
470
-			$this->file_put_contents($path, '');
471
-		}
472
-		return true;
473
-	}
474
-
475
-	/**
476
-	 * @param string $path
477
-	 * @param string $data
478
-	 * @return int
479
-	 */
480
-	public function file_put_contents($path, $data) {
481
-		$path = $this->cleanPath($path);
482
-		$result = parent::file_put_contents($path, $data);
483
-		$this->statCache->remove($path);
484
-		return $result;
485
-	}
486
-
487
-	/**
488
-	 * @param string $path
489
-	 * @param string $target
490
-	 */
491
-	protected function uploadFile($path, $target) {
492
-		$this->init();
493
-
494
-		// invalidate
495
-		$target = $this->cleanPath($target);
496
-		$this->statCache->remove($target);
497
-		$source = fopen($path, 'r');
498
-
499
-		$this->httpClientService
500
-			->newClient()
501
-			->put($this->createBaseUri() . $this->encodePath($target), [
502
-				'body' => $source,
503
-				'auth' => [$this->user, $this->password]
504
-			]);
505
-
506
-		$this->removeCachedFile($target);
507
-	}
508
-
509
-	/** {@inheritdoc} */
510
-	public function rename($path1, $path2) {
511
-		$this->init();
512
-		$path1 = $this->cleanPath($path1);
513
-		$path2 = $this->cleanPath($path2);
514
-		try {
515
-			// overwrite directory ?
516
-			if ($this->is_dir($path2)) {
517
-				// needs trailing slash in destination
518
-				$path2 = rtrim($path2, '/') . '/';
519
-			}
520
-			$this->client->request(
521
-				'MOVE',
522
-				$this->encodePath($path1),
523
-				null,
524
-				[
525
-					'Destination' => $this->createBaseUri() . $this->encodePath($path2),
526
-				]
527
-			);
528
-			$this->statCache->clear($path1 . '/');
529
-			$this->statCache->clear($path2 . '/');
530
-			$this->statCache->set($path1, false);
531
-			$this->statCache->set($path2, true);
532
-			$this->removeCachedFile($path1);
533
-			$this->removeCachedFile($path2);
534
-			return true;
535
-		} catch (\Exception $e) {
536
-			$this->convertException($e);
537
-		}
538
-		return false;
539
-	}
540
-
541
-	/** {@inheritdoc} */
542
-	public function copy($path1, $path2) {
543
-		$this->init();
544
-		$path1 = $this->cleanPath($path1);
545
-		$path2 = $this->cleanPath($path2);
546
-		try {
547
-			// overwrite directory ?
548
-			if ($this->is_dir($path2)) {
549
-				// needs trailing slash in destination
550
-				$path2 = rtrim($path2, '/') . '/';
551
-			}
552
-			$this->client->request(
553
-				'COPY',
554
-				$this->encodePath($path1),
555
-				null,
556
-				[
557
-					'Destination' => $this->createBaseUri() . $this->encodePath($path2),
558
-				]
559
-			);
560
-			$this->statCache->clear($path2 . '/');
561
-			$this->statCache->set($path2, true);
562
-			$this->removeCachedFile($path2);
563
-			return true;
564
-		} catch (\Exception $e) {
565
-			$this->convertException($e);
566
-		}
567
-		return false;
568
-	}
569
-
570
-	/** {@inheritdoc} */
571
-	public function stat($path) {
572
-		try {
573
-			$response = $this->propfind($path);
574
-			if (!$response) {
575
-				return false;
576
-			}
577
-			return [
578
-				'mtime' => strtotime($response['{DAV:}getlastmodified']),
579
-				'size' => (int)isset($response['{DAV:}getcontentlength']) ? $response['{DAV:}getcontentlength'] : 0,
580
-			];
581
-		} catch (\Exception $e) {
582
-			$this->convertException($e, $path);
583
-		}
584
-		return array();
585
-	}
586
-
587
-	/** {@inheritdoc} */
588
-	public function getMimeType($path) {
589
-		$remoteMimetype = $this->getMimeTypeFromRemote($path);
590
-		if ($remoteMimetype === 'application/octet-stream') {
591
-			return \OC::$server->getMimeTypeDetector()->detectPath($path);
592
-		} else {
593
-			return $remoteMimetype;
594
-		}
595
-	}
596
-
597
-	public function getMimeTypeFromRemote($path) {
598
-		try {
599
-			$response = $this->propfind($path);
600
-			if ($response === false) {
601
-				return false;
602
-			}
603
-			$responseType = [];
604
-			if (isset($response["{DAV:}resourcetype"])) {
605
-				/** @var ResourceType[] $response */
606
-				$responseType = $response["{DAV:}resourcetype"]->getValue();
607
-			}
608
-			$type = (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file';
609
-			if ($type == 'dir') {
610
-				return 'httpd/unix-directory';
611
-			} elseif (isset($response['{DAV:}getcontenttype'])) {
612
-				return $response['{DAV:}getcontenttype'];
613
-			} else {
614
-				return 'application/octet-stream';
615
-			}
616
-		} catch (\Exception $e) {
617
-			return false;
618
-		}
619
-	}
620
-
621
-	/**
622
-	 * @param string $path
623
-	 * @return string
624
-	 */
625
-	public function cleanPath($path) {
626
-		if ($path === '') {
627
-			return $path;
628
-		}
629
-		$path = Filesystem::normalizePath($path);
630
-		// remove leading slash
631
-		return substr($path, 1);
632
-	}
633
-
634
-	/**
635
-	 * URL encodes the given path but keeps the slashes
636
-	 *
637
-	 * @param string $path to encode
638
-	 * @return string encoded path
639
-	 */
640
-	protected function encodePath($path) {
641
-		// slashes need to stay
642
-		return str_replace('%2F', '/', rawurlencode($path));
643
-	}
644
-
645
-	/**
646
-	 * @param string $method
647
-	 * @param string $path
648
-	 * @param string|resource|null $body
649
-	 * @param int $expected
650
-	 * @return bool
651
-	 * @throws StorageInvalidException
652
-	 * @throws StorageNotAvailableException
653
-	 */
654
-	protected function simpleResponse($method, $path, $body, $expected) {
655
-		$path = $this->cleanPath($path);
656
-		try {
657
-			$response = $this->client->request($method, $this->encodePath($path), $body);
658
-			return $response['statusCode'] == $expected;
659
-		} catch (ClientHttpException $e) {
660
-			if ($e->getHttpStatus() === 404 && $method === 'DELETE') {
661
-				$this->statCache->clear($path . '/');
662
-				$this->statCache->set($path, false);
663
-				return false;
664
-			}
665
-
666
-			$this->convertException($e, $path);
667
-		} catch (\Exception $e) {
668
-			$this->convertException($e, $path);
669
-		}
670
-		return false;
671
-	}
672
-
673
-	/**
674
-	 * check if curl is installed
675
-	 */
676
-	public static function checkDependencies() {
677
-		return true;
678
-	}
679
-
680
-	/** {@inheritdoc} */
681
-	public function isUpdatable($path) {
682
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_UPDATE);
683
-	}
684
-
685
-	/** {@inheritdoc} */
686
-	public function isCreatable($path) {
687
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_CREATE);
688
-	}
689
-
690
-	/** {@inheritdoc} */
691
-	public function isSharable($path) {
692
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE);
693
-	}
694
-
695
-	/** {@inheritdoc} */
696
-	public function isDeletable($path) {
697
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_DELETE);
698
-	}
699
-
700
-	/** {@inheritdoc} */
701
-	public function getPermissions($path) {
702
-		$this->init();
703
-		$path = $this->cleanPath($path);
704
-		$response = $this->propfind($path);
705
-		if ($response === false) {
706
-			return 0;
707
-		}
708
-		if (isset($response['{http://owncloud.org/ns}permissions'])) {
709
-			return $this->parsePermissions($response['{http://owncloud.org/ns}permissions']);
710
-		} else if ($this->is_dir($path)) {
711
-			return Constants::PERMISSION_ALL;
712
-		} else if ($this->file_exists($path)) {
713
-			return Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE;
714
-		} else {
715
-			return 0;
716
-		}
717
-	}
718
-
719
-	/** {@inheritdoc} */
720
-	public function getETag($path) {
721
-		$this->init();
722
-		$path = $this->cleanPath($path);
723
-		$response = $this->propfind($path);
724
-		if ($response === false) {
725
-			return null;
726
-		}
727
-		if (isset($response['{DAV:}getetag'])) {
728
-			return trim($response['{DAV:}getetag'], '"');
729
-		}
730
-		return parent::getEtag($path);
731
-	}
732
-
733
-	/**
734
-	 * @param string $permissionsString
735
-	 * @return int
736
-	 */
737
-	protected function parsePermissions($permissionsString) {
738
-		$permissions = Constants::PERMISSION_READ;
739
-		if (strpos($permissionsString, 'R') !== false) {
740
-			$permissions |= Constants::PERMISSION_SHARE;
741
-		}
742
-		if (strpos($permissionsString, 'D') !== false) {
743
-			$permissions |= Constants::PERMISSION_DELETE;
744
-		}
745
-		if (strpos($permissionsString, 'W') !== false) {
746
-			$permissions |= Constants::PERMISSION_UPDATE;
747
-		}
748
-		if (strpos($permissionsString, 'CK') !== false) {
749
-			$permissions |= Constants::PERMISSION_CREATE;
750
-			$permissions |= Constants::PERMISSION_UPDATE;
751
-		}
752
-		return $permissions;
753
-	}
754
-
755
-	/**
756
-	 * check if a file or folder has been updated since $time
757
-	 *
758
-	 * @param string $path
759
-	 * @param int $time
760
-	 * @throws \OCP\Files\StorageNotAvailableException
761
-	 * @return bool
762
-	 */
763
-	public function hasUpdated($path, $time) {
764
-		$this->init();
765
-		$path = $this->cleanPath($path);
766
-		try {
767
-			// force refresh for $path
768
-			$this->statCache->remove($path);
769
-			$response = $this->propfind($path);
770
-			if ($response === false) {
771
-				if ($path === '') {
772
-					// if root is gone it means the storage is not available
773
-					throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
774
-				}
775
-				return false;
776
-			}
777
-			if (isset($response['{DAV:}getetag'])) {
778
-				$cachedData = $this->getCache()->get($path);
779
-				$etag = null;
780
-				if (isset($response['{DAV:}getetag'])) {
781
-					$etag = trim($response['{DAV:}getetag'], '"');
782
-				}
783
-				if (!empty($etag) && $cachedData['etag'] !== $etag) {
784
-					return true;
785
-				} else if (isset($response['{http://open-collaboration-services.org/ns}share-permissions'])) {
786
-					$sharePermissions = (int)$response['{http://open-collaboration-services.org/ns}share-permissions'];
787
-					return $sharePermissions !== $cachedData['permissions'];
788
-				} else if (isset($response['{http://owncloud.org/ns}permissions'])) {
789
-					$permissions = $this->parsePermissions($response['{http://owncloud.org/ns}permissions']);
790
-					return $permissions !== $cachedData['permissions'];
791
-				} else {
792
-					return false;
793
-				}
794
-			} else {
795
-				$remoteMtime = strtotime($response['{DAV:}getlastmodified']);
796
-				return $remoteMtime > $time;
797
-			}
798
-		} catch (ClientHttpException $e) {
799
-			if ($e->getHttpStatus() === 405) {
800
-				if ($path === '') {
801
-					// if root is gone it means the storage is not available
802
-					throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
803
-				}
804
-				return false;
805
-			}
806
-			$this->convertException($e, $path);
807
-			return false;
808
-		} catch (\Exception $e) {
809
-			$this->convertException($e, $path);
810
-			return false;
811
-		}
812
-	}
813
-
814
-	/**
815
-	 * Interpret the given exception and decide whether it is due to an
816
-	 * unavailable storage, invalid storage or other.
817
-	 * This will either throw StorageInvalidException, StorageNotAvailableException
818
-	 * or do nothing.
819
-	 *
820
-	 * @param Exception $e sabre exception
821
-	 * @param string $path optional path from the operation
822
-	 *
823
-	 * @throws StorageInvalidException if the storage is invalid, for example
824
-	 * when the authentication expired or is invalid
825
-	 * @throws StorageNotAvailableException if the storage is not available,
826
-	 * which might be temporary
827
-	 */
828
-	protected function convertException(Exception $e, $path = '') {
829
-		\OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
830
-		if ($e instanceof ClientHttpException) {
831
-			if ($e->getHttpStatus() === Http::STATUS_LOCKED) {
832
-				throw new \OCP\Lock\LockedException($path);
833
-			}
834
-			if ($e->getHttpStatus() === Http::STATUS_UNAUTHORIZED) {
835
-				// either password was changed or was invalid all along
836
-				throw new StorageInvalidException(get_class($e) . ': ' . $e->getMessage());
837
-			} else if ($e->getHttpStatus() === Http::STATUS_METHOD_NOT_ALLOWED) {
838
-				// ignore exception for MethodNotAllowed, false will be returned
839
-				return;
840
-			}
841
-			throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
842
-		} else if ($e instanceof ClientException) {
843
-			// connection timeout or refused, server could be temporarily down
844
-			throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
845
-		} else if ($e instanceof \InvalidArgumentException) {
846
-			// parse error because the server returned HTML instead of XML,
847
-			// possibly temporarily down
848
-			throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
849
-		} else if (($e instanceof StorageNotAvailableException) || ($e instanceof StorageInvalidException)) {
850
-			// rethrow
851
-			throw $e;
852
-		}
853
-
854
-		// TODO: only log for now, but in the future need to wrap/rethrow exception
855
-	}
60
+    /** @var string */
61
+    protected $password;
62
+    /** @var string */
63
+    protected $user;
64
+    /** @var string */
65
+    protected $authType;
66
+    /** @var string */
67
+    protected $host;
68
+    /** @var bool */
69
+    protected $secure;
70
+    /** @var string */
71
+    protected $root;
72
+    /** @var string */
73
+    protected $certPath;
74
+    /** @var bool */
75
+    protected $ready;
76
+    /** @var Client */
77
+    protected $client;
78
+    /** @var ArrayCache */
79
+    protected $statCache;
80
+    /** @var \OCP\Http\Client\IClientService */
81
+    protected $httpClientService;
82
+
83
+    /**
84
+     * @param array $params
85
+     * @throws \Exception
86
+     */
87
+    public function __construct($params) {
88
+        $this->statCache = new ArrayCache();
89
+        $this->httpClientService = \OC::$server->getHTTPClientService();
90
+        if (isset($params['host']) && isset($params['user']) && isset($params['password'])) {
91
+            $host = $params['host'];
92
+            //remove leading http[s], will be generated in createBaseUri()
93
+            if (substr($host, 0, 8) == "https://") $host = substr($host, 8);
94
+            else if (substr($host, 0, 7) == "http://") $host = substr($host, 7);
95
+            $this->host = $host;
96
+            $this->user = $params['user'];
97
+            $this->password = $params['password'];
98
+            if (isset($params['authType'])) {
99
+                $this->authType = $params['authType'];
100
+            }
101
+            if (isset($params['secure'])) {
102
+                if (is_string($params['secure'])) {
103
+                    $this->secure = ($params['secure'] === 'true');
104
+                } else {
105
+                    $this->secure = (bool)$params['secure'];
106
+                }
107
+            } else {
108
+                $this->secure = false;
109
+            }
110
+            if ($this->secure === true) {
111
+                // inject mock for testing
112
+                $certManager = \OC::$server->getCertificateManager();
113
+                if (is_null($certManager)) { //no user
114
+                    $certManager = \OC::$server->getCertificateManager(null);
115
+                }
116
+                $certPath = $certManager->getAbsoluteBundlePath();
117
+                if (file_exists($certPath)) {
118
+                    $this->certPath = $certPath;
119
+                }
120
+            }
121
+            $this->root = $params['root'] ?? '/';
122
+            $this->root = '/' . ltrim($this->root, '/');
123
+            $this->root = rtrim($this->root, '/') . '/';
124
+        } else {
125
+            throw new \Exception('Invalid webdav storage configuration');
126
+        }
127
+    }
128
+
129
+    protected function init() {
130
+        if ($this->ready) {
131
+            return;
132
+        }
133
+        $this->ready = true;
134
+
135
+        $settings = [
136
+            'baseUri' => $this->createBaseUri(),
137
+            'userName' => $this->user,
138
+            'password' => $this->password,
139
+        ];
140
+        if (isset($this->authType)) {
141
+            $settings['authType'] = $this->authType;
142
+        }
143
+
144
+        $proxy = \OC::$server->getConfig()->getSystemValue('proxy', '');
145
+        if ($proxy !== '') {
146
+            $settings['proxy'] = $proxy;
147
+        }
148
+
149
+        $this->client = new Client($settings);
150
+        $this->client->setThrowExceptions(true);
151
+        if ($this->secure === true && $this->certPath) {
152
+            $this->client->addCurlSetting(CURLOPT_CAINFO, $this->certPath);
153
+        }
154
+    }
155
+
156
+    /**
157
+     * Clear the stat cache
158
+     */
159
+    public function clearStatCache() {
160
+        $this->statCache->clear();
161
+    }
162
+
163
+    /** {@inheritdoc} */
164
+    public function getId() {
165
+        return 'webdav::' . $this->user . '@' . $this->host . '/' . $this->root;
166
+    }
167
+
168
+    /** {@inheritdoc} */
169
+    public function createBaseUri() {
170
+        $baseUri = 'http';
171
+        if ($this->secure) {
172
+            $baseUri .= 's';
173
+        }
174
+        $baseUri .= '://' . $this->host . $this->root;
175
+        return $baseUri;
176
+    }
177
+
178
+    /** {@inheritdoc} */
179
+    public function mkdir($path) {
180
+        $this->init();
181
+        $path = $this->cleanPath($path);
182
+        $result = $this->simpleResponse('MKCOL', $path, null, 201);
183
+        if ($result) {
184
+            $this->statCache->set($path, true);
185
+        }
186
+        return $result;
187
+    }
188
+
189
+    /** {@inheritdoc} */
190
+    public function rmdir($path) {
191
+        $this->init();
192
+        $path = $this->cleanPath($path);
193
+        // FIXME: some WebDAV impl return 403 when trying to DELETE
194
+        // a non-empty folder
195
+        $result = $this->simpleResponse('DELETE', $path . '/', null, 204);
196
+        $this->statCache->clear($path . '/');
197
+        $this->statCache->remove($path);
198
+        return $result;
199
+    }
200
+
201
+    /** {@inheritdoc} */
202
+    public function opendir($path) {
203
+        $this->init();
204
+        $path = $this->cleanPath($path);
205
+        try {
206
+            $response = $this->client->propFind(
207
+                $this->encodePath($path),
208
+                ['{DAV:}href'],
209
+                1
210
+            );
211
+            if ($response === false) {
212
+                return false;
213
+            }
214
+            $content = [];
215
+            $files = array_keys($response);
216
+            array_shift($files); //the first entry is the current directory
217
+
218
+            if (!$this->statCache->hasKey($path)) {
219
+                $this->statCache->set($path, true);
220
+            }
221
+            foreach ($files as $file) {
222
+                $file = urldecode($file);
223
+                // do not store the real entry, we might not have all properties
224
+                if (!$this->statCache->hasKey($path)) {
225
+                    $this->statCache->set($file, true);
226
+                }
227
+                $file = basename($file);
228
+                $content[] = $file;
229
+            }
230
+            return IteratorDirectory::wrap($content);
231
+        } catch (\Exception $e) {
232
+            $this->convertException($e, $path);
233
+        }
234
+        return false;
235
+    }
236
+
237
+    /**
238
+     * Propfind call with cache handling.
239
+     *
240
+     * First checks if information is cached.
241
+     * If not, request it from the server then store to cache.
242
+     *
243
+     * @param string $path path to propfind
244
+     *
245
+     * @return array|boolean propfind response or false if the entry was not found
246
+     *
247
+     * @throws ClientHttpException
248
+     */
249
+    protected function propfind($path) {
250
+        $path = $this->cleanPath($path);
251
+        $cachedResponse = $this->statCache->get($path);
252
+        // we either don't know it, or we know it exists but need more details
253
+        if (is_null($cachedResponse) || $cachedResponse === true) {
254
+            $this->init();
255
+            try {
256
+                $response = $this->client->propFind(
257
+                    $this->encodePath($path),
258
+                    array(
259
+                        '{DAV:}getlastmodified',
260
+                        '{DAV:}getcontentlength',
261
+                        '{DAV:}getcontenttype',
262
+                        '{http://owncloud.org/ns}permissions',
263
+                        '{http://open-collaboration-services.org/ns}share-permissions',
264
+                        '{DAV:}resourcetype',
265
+                        '{DAV:}getetag',
266
+                    )
267
+                );
268
+                $this->statCache->set($path, $response);
269
+            } catch (ClientHttpException $e) {
270
+                if ($e->getHttpStatus() === 404) {
271
+                    $this->statCache->clear($path . '/');
272
+                    $this->statCache->set($path, false);
273
+                    return false;
274
+                }
275
+                $this->convertException($e, $path);
276
+            } catch (\Exception $e) {
277
+                $this->convertException($e, $path);
278
+            }
279
+        } else {
280
+            $response = $cachedResponse;
281
+        }
282
+        return $response;
283
+    }
284
+
285
+    /** {@inheritdoc} */
286
+    public function filetype($path) {
287
+        try {
288
+            $response = $this->propfind($path);
289
+            if ($response === false) {
290
+                return false;
291
+            }
292
+            $responseType = [];
293
+            if (isset($response["{DAV:}resourcetype"])) {
294
+                /** @var ResourceType[] $response */
295
+                $responseType = $response["{DAV:}resourcetype"]->getValue();
296
+            }
297
+            return (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file';
298
+        } catch (\Exception $e) {
299
+            $this->convertException($e, $path);
300
+        }
301
+        return false;
302
+    }
303
+
304
+    /** {@inheritdoc} */
305
+    public function file_exists($path) {
306
+        try {
307
+            $path = $this->cleanPath($path);
308
+            $cachedState = $this->statCache->get($path);
309
+            if ($cachedState === false) {
310
+                // we know the file doesn't exist
311
+                return false;
312
+            } else if (!is_null($cachedState)) {
313
+                return true;
314
+            }
315
+            // need to get from server
316
+            return ($this->propfind($path) !== false);
317
+        } catch (\Exception $e) {
318
+            $this->convertException($e, $path);
319
+        }
320
+        return false;
321
+    }
322
+
323
+    /** {@inheritdoc} */
324
+    public function unlink($path) {
325
+        $this->init();
326
+        $path = $this->cleanPath($path);
327
+        $result = $this->simpleResponse('DELETE', $path, null, 204);
328
+        $this->statCache->clear($path . '/');
329
+        $this->statCache->remove($path);
330
+        return $result;
331
+    }
332
+
333
+    /** {@inheritdoc} */
334
+    public function fopen($path, $mode) {
335
+        $this->init();
336
+        $path = $this->cleanPath($path);
337
+        switch ($mode) {
338
+            case 'r':
339
+            case 'rb':
340
+                try {
341
+                    $response = $this->httpClientService
342
+                        ->newClient()
343
+                        ->get($this->createBaseUri() . $this->encodePath($path), [
344
+                            'auth' => [$this->user, $this->password],
345
+                            'stream' => true
346
+                        ]);
347
+                } catch (\GuzzleHttp\Exception\ClientException $e) {
348
+                    if ($e->getResponse() instanceof ResponseInterface
349
+                        && $e->getResponse()->getStatusCode() === 404) {
350
+                        return false;
351
+                    } else {
352
+                        throw $e;
353
+                    }
354
+                }
355
+
356
+                if ($response->getStatusCode() !== Http::STATUS_OK) {
357
+                    if ($response->getStatusCode() === Http::STATUS_LOCKED) {
358
+                        throw new \OCP\Lock\LockedException($path);
359
+                    } else {
360
+                        Util::writeLog("webdav client", 'Guzzle get returned status code ' . $response->getStatusCode(), Util::ERROR);
361
+                    }
362
+                }
363
+
364
+                return $response->getBody();
365
+            case 'w':
366
+            case 'wb':
367
+            case 'a':
368
+            case 'ab':
369
+            case 'r+':
370
+            case 'w+':
371
+            case 'wb+':
372
+            case 'a+':
373
+            case 'x':
374
+            case 'x+':
375
+            case 'c':
376
+            case 'c+':
377
+                //emulate these
378
+                $tempManager = \OC::$server->getTempManager();
379
+                if (strrpos($path, '.') !== false) {
380
+                    $ext = substr($path, strrpos($path, '.'));
381
+                } else {
382
+                    $ext = '';
383
+                }
384
+                if ($this->file_exists($path)) {
385
+                    if (!$this->isUpdatable($path)) {
386
+                        return false;
387
+                    }
388
+                    if ($mode === 'w' or $mode === 'w+') {
389
+                        $tmpFile = $tempManager->getTemporaryFile($ext);
390
+                    } else {
391
+                        $tmpFile = $this->getCachedFile($path);
392
+                    }
393
+                } else {
394
+                    if (!$this->isCreatable(dirname($path))) {
395
+                        return false;
396
+                    }
397
+                    $tmpFile = $tempManager->getTemporaryFile($ext);
398
+                }
399
+                $handle = fopen($tmpFile, $mode);
400
+                return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
401
+                    $this->writeBack($tmpFile, $path);
402
+                });
403
+        }
404
+    }
405
+
406
+    /**
407
+     * @param string $tmpFile
408
+     */
409
+    public function writeBack($tmpFile, $path) {
410
+        $this->uploadFile($tmpFile, $path);
411
+        unlink($tmpFile);
412
+    }
413
+
414
+    /** {@inheritdoc} */
415
+    public function free_space($path) {
416
+        $this->init();
417
+        $path = $this->cleanPath($path);
418
+        try {
419
+            // TODO: cacheable ?
420
+            $response = $this->client->propfind($this->encodePath($path), ['{DAV:}quota-available-bytes']);
421
+            if ($response === false) {
422
+                return FileInfo::SPACE_UNKNOWN;
423
+            }
424
+            if (isset($response['{DAV:}quota-available-bytes'])) {
425
+                return (int)$response['{DAV:}quota-available-bytes'];
426
+            } else {
427
+                return FileInfo::SPACE_UNKNOWN;
428
+            }
429
+        } catch (\Exception $e) {
430
+            return FileInfo::SPACE_UNKNOWN;
431
+        }
432
+    }
433
+
434
+    /** {@inheritdoc} */
435
+    public function touch($path, $mtime = null) {
436
+        $this->init();
437
+        if (is_null($mtime)) {
438
+            $mtime = time();
439
+        }
440
+        $path = $this->cleanPath($path);
441
+
442
+        // if file exists, update the mtime, else create a new empty file
443
+        if ($this->file_exists($path)) {
444
+            try {
445
+                $this->statCache->remove($path);
446
+                $this->client->proppatch($this->encodePath($path), ['{DAV:}lastmodified' => $mtime]);
447
+                // non-owncloud clients might not have accepted the property, need to recheck it
448
+                $response = $this->client->propfind($this->encodePath($path), ['{DAV:}getlastmodified'], 0);
449
+                if ($response === false) {
450
+                    return false;
451
+                }
452
+                if (isset($response['{DAV:}getlastmodified'])) {
453
+                    $remoteMtime = strtotime($response['{DAV:}getlastmodified']);
454
+                    if ($remoteMtime !== $mtime) {
455
+                        // server has not accepted the mtime
456
+                        return false;
457
+                    }
458
+                }
459
+            } catch (ClientHttpException $e) {
460
+                if ($e->getHttpStatus() === 501) {
461
+                    return false;
462
+                }
463
+                $this->convertException($e, $path);
464
+                return false;
465
+            } catch (\Exception $e) {
466
+                $this->convertException($e, $path);
467
+                return false;
468
+            }
469
+        } else {
470
+            $this->file_put_contents($path, '');
471
+        }
472
+        return true;
473
+    }
474
+
475
+    /**
476
+     * @param string $path
477
+     * @param string $data
478
+     * @return int
479
+     */
480
+    public function file_put_contents($path, $data) {
481
+        $path = $this->cleanPath($path);
482
+        $result = parent::file_put_contents($path, $data);
483
+        $this->statCache->remove($path);
484
+        return $result;
485
+    }
486
+
487
+    /**
488
+     * @param string $path
489
+     * @param string $target
490
+     */
491
+    protected function uploadFile($path, $target) {
492
+        $this->init();
493
+
494
+        // invalidate
495
+        $target = $this->cleanPath($target);
496
+        $this->statCache->remove($target);
497
+        $source = fopen($path, 'r');
498
+
499
+        $this->httpClientService
500
+            ->newClient()
501
+            ->put($this->createBaseUri() . $this->encodePath($target), [
502
+                'body' => $source,
503
+                'auth' => [$this->user, $this->password]
504
+            ]);
505
+
506
+        $this->removeCachedFile($target);
507
+    }
508
+
509
+    /** {@inheritdoc} */
510
+    public function rename($path1, $path2) {
511
+        $this->init();
512
+        $path1 = $this->cleanPath($path1);
513
+        $path2 = $this->cleanPath($path2);
514
+        try {
515
+            // overwrite directory ?
516
+            if ($this->is_dir($path2)) {
517
+                // needs trailing slash in destination
518
+                $path2 = rtrim($path2, '/') . '/';
519
+            }
520
+            $this->client->request(
521
+                'MOVE',
522
+                $this->encodePath($path1),
523
+                null,
524
+                [
525
+                    'Destination' => $this->createBaseUri() . $this->encodePath($path2),
526
+                ]
527
+            );
528
+            $this->statCache->clear($path1 . '/');
529
+            $this->statCache->clear($path2 . '/');
530
+            $this->statCache->set($path1, false);
531
+            $this->statCache->set($path2, true);
532
+            $this->removeCachedFile($path1);
533
+            $this->removeCachedFile($path2);
534
+            return true;
535
+        } catch (\Exception $e) {
536
+            $this->convertException($e);
537
+        }
538
+        return false;
539
+    }
540
+
541
+    /** {@inheritdoc} */
542
+    public function copy($path1, $path2) {
543
+        $this->init();
544
+        $path1 = $this->cleanPath($path1);
545
+        $path2 = $this->cleanPath($path2);
546
+        try {
547
+            // overwrite directory ?
548
+            if ($this->is_dir($path2)) {
549
+                // needs trailing slash in destination
550
+                $path2 = rtrim($path2, '/') . '/';
551
+            }
552
+            $this->client->request(
553
+                'COPY',
554
+                $this->encodePath($path1),
555
+                null,
556
+                [
557
+                    'Destination' => $this->createBaseUri() . $this->encodePath($path2),
558
+                ]
559
+            );
560
+            $this->statCache->clear($path2 . '/');
561
+            $this->statCache->set($path2, true);
562
+            $this->removeCachedFile($path2);
563
+            return true;
564
+        } catch (\Exception $e) {
565
+            $this->convertException($e);
566
+        }
567
+        return false;
568
+    }
569
+
570
+    /** {@inheritdoc} */
571
+    public function stat($path) {
572
+        try {
573
+            $response = $this->propfind($path);
574
+            if (!$response) {
575
+                return false;
576
+            }
577
+            return [
578
+                'mtime' => strtotime($response['{DAV:}getlastmodified']),
579
+                'size' => (int)isset($response['{DAV:}getcontentlength']) ? $response['{DAV:}getcontentlength'] : 0,
580
+            ];
581
+        } catch (\Exception $e) {
582
+            $this->convertException($e, $path);
583
+        }
584
+        return array();
585
+    }
586
+
587
+    /** {@inheritdoc} */
588
+    public function getMimeType($path) {
589
+        $remoteMimetype = $this->getMimeTypeFromRemote($path);
590
+        if ($remoteMimetype === 'application/octet-stream') {
591
+            return \OC::$server->getMimeTypeDetector()->detectPath($path);
592
+        } else {
593
+            return $remoteMimetype;
594
+        }
595
+    }
596
+
597
+    public function getMimeTypeFromRemote($path) {
598
+        try {
599
+            $response = $this->propfind($path);
600
+            if ($response === false) {
601
+                return false;
602
+            }
603
+            $responseType = [];
604
+            if (isset($response["{DAV:}resourcetype"])) {
605
+                /** @var ResourceType[] $response */
606
+                $responseType = $response["{DAV:}resourcetype"]->getValue();
607
+            }
608
+            $type = (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file';
609
+            if ($type == 'dir') {
610
+                return 'httpd/unix-directory';
611
+            } elseif (isset($response['{DAV:}getcontenttype'])) {
612
+                return $response['{DAV:}getcontenttype'];
613
+            } else {
614
+                return 'application/octet-stream';
615
+            }
616
+        } catch (\Exception $e) {
617
+            return false;
618
+        }
619
+    }
620
+
621
+    /**
622
+     * @param string $path
623
+     * @return string
624
+     */
625
+    public function cleanPath($path) {
626
+        if ($path === '') {
627
+            return $path;
628
+        }
629
+        $path = Filesystem::normalizePath($path);
630
+        // remove leading slash
631
+        return substr($path, 1);
632
+    }
633
+
634
+    /**
635
+     * URL encodes the given path but keeps the slashes
636
+     *
637
+     * @param string $path to encode
638
+     * @return string encoded path
639
+     */
640
+    protected function encodePath($path) {
641
+        // slashes need to stay
642
+        return str_replace('%2F', '/', rawurlencode($path));
643
+    }
644
+
645
+    /**
646
+     * @param string $method
647
+     * @param string $path
648
+     * @param string|resource|null $body
649
+     * @param int $expected
650
+     * @return bool
651
+     * @throws StorageInvalidException
652
+     * @throws StorageNotAvailableException
653
+     */
654
+    protected function simpleResponse($method, $path, $body, $expected) {
655
+        $path = $this->cleanPath($path);
656
+        try {
657
+            $response = $this->client->request($method, $this->encodePath($path), $body);
658
+            return $response['statusCode'] == $expected;
659
+        } catch (ClientHttpException $e) {
660
+            if ($e->getHttpStatus() === 404 && $method === 'DELETE') {
661
+                $this->statCache->clear($path . '/');
662
+                $this->statCache->set($path, false);
663
+                return false;
664
+            }
665
+
666
+            $this->convertException($e, $path);
667
+        } catch (\Exception $e) {
668
+            $this->convertException($e, $path);
669
+        }
670
+        return false;
671
+    }
672
+
673
+    /**
674
+     * check if curl is installed
675
+     */
676
+    public static function checkDependencies() {
677
+        return true;
678
+    }
679
+
680
+    /** {@inheritdoc} */
681
+    public function isUpdatable($path) {
682
+        return (bool)($this->getPermissions($path) & Constants::PERMISSION_UPDATE);
683
+    }
684
+
685
+    /** {@inheritdoc} */
686
+    public function isCreatable($path) {
687
+        return (bool)($this->getPermissions($path) & Constants::PERMISSION_CREATE);
688
+    }
689
+
690
+    /** {@inheritdoc} */
691
+    public function isSharable($path) {
692
+        return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE);
693
+    }
694
+
695
+    /** {@inheritdoc} */
696
+    public function isDeletable($path) {
697
+        return (bool)($this->getPermissions($path) & Constants::PERMISSION_DELETE);
698
+    }
699
+
700
+    /** {@inheritdoc} */
701
+    public function getPermissions($path) {
702
+        $this->init();
703
+        $path = $this->cleanPath($path);
704
+        $response = $this->propfind($path);
705
+        if ($response === false) {
706
+            return 0;
707
+        }
708
+        if (isset($response['{http://owncloud.org/ns}permissions'])) {
709
+            return $this->parsePermissions($response['{http://owncloud.org/ns}permissions']);
710
+        } else if ($this->is_dir($path)) {
711
+            return Constants::PERMISSION_ALL;
712
+        } else if ($this->file_exists($path)) {
713
+            return Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE;
714
+        } else {
715
+            return 0;
716
+        }
717
+    }
718
+
719
+    /** {@inheritdoc} */
720
+    public function getETag($path) {
721
+        $this->init();
722
+        $path = $this->cleanPath($path);
723
+        $response = $this->propfind($path);
724
+        if ($response === false) {
725
+            return null;
726
+        }
727
+        if (isset($response['{DAV:}getetag'])) {
728
+            return trim($response['{DAV:}getetag'], '"');
729
+        }
730
+        return parent::getEtag($path);
731
+    }
732
+
733
+    /**
734
+     * @param string $permissionsString
735
+     * @return int
736
+     */
737
+    protected function parsePermissions($permissionsString) {
738
+        $permissions = Constants::PERMISSION_READ;
739
+        if (strpos($permissionsString, 'R') !== false) {
740
+            $permissions |= Constants::PERMISSION_SHARE;
741
+        }
742
+        if (strpos($permissionsString, 'D') !== false) {
743
+            $permissions |= Constants::PERMISSION_DELETE;
744
+        }
745
+        if (strpos($permissionsString, 'W') !== false) {
746
+            $permissions |= Constants::PERMISSION_UPDATE;
747
+        }
748
+        if (strpos($permissionsString, 'CK') !== false) {
749
+            $permissions |= Constants::PERMISSION_CREATE;
750
+            $permissions |= Constants::PERMISSION_UPDATE;
751
+        }
752
+        return $permissions;
753
+    }
754
+
755
+    /**
756
+     * check if a file or folder has been updated since $time
757
+     *
758
+     * @param string $path
759
+     * @param int $time
760
+     * @throws \OCP\Files\StorageNotAvailableException
761
+     * @return bool
762
+     */
763
+    public function hasUpdated($path, $time) {
764
+        $this->init();
765
+        $path = $this->cleanPath($path);
766
+        try {
767
+            // force refresh for $path
768
+            $this->statCache->remove($path);
769
+            $response = $this->propfind($path);
770
+            if ($response === false) {
771
+                if ($path === '') {
772
+                    // if root is gone it means the storage is not available
773
+                    throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
774
+                }
775
+                return false;
776
+            }
777
+            if (isset($response['{DAV:}getetag'])) {
778
+                $cachedData = $this->getCache()->get($path);
779
+                $etag = null;
780
+                if (isset($response['{DAV:}getetag'])) {
781
+                    $etag = trim($response['{DAV:}getetag'], '"');
782
+                }
783
+                if (!empty($etag) && $cachedData['etag'] !== $etag) {
784
+                    return true;
785
+                } else if (isset($response['{http://open-collaboration-services.org/ns}share-permissions'])) {
786
+                    $sharePermissions = (int)$response['{http://open-collaboration-services.org/ns}share-permissions'];
787
+                    return $sharePermissions !== $cachedData['permissions'];
788
+                } else if (isset($response['{http://owncloud.org/ns}permissions'])) {
789
+                    $permissions = $this->parsePermissions($response['{http://owncloud.org/ns}permissions']);
790
+                    return $permissions !== $cachedData['permissions'];
791
+                } else {
792
+                    return false;
793
+                }
794
+            } else {
795
+                $remoteMtime = strtotime($response['{DAV:}getlastmodified']);
796
+                return $remoteMtime > $time;
797
+            }
798
+        } catch (ClientHttpException $e) {
799
+            if ($e->getHttpStatus() === 405) {
800
+                if ($path === '') {
801
+                    // if root is gone it means the storage is not available
802
+                    throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
803
+                }
804
+                return false;
805
+            }
806
+            $this->convertException($e, $path);
807
+            return false;
808
+        } catch (\Exception $e) {
809
+            $this->convertException($e, $path);
810
+            return false;
811
+        }
812
+    }
813
+
814
+    /**
815
+     * Interpret the given exception and decide whether it is due to an
816
+     * unavailable storage, invalid storage or other.
817
+     * This will either throw StorageInvalidException, StorageNotAvailableException
818
+     * or do nothing.
819
+     *
820
+     * @param Exception $e sabre exception
821
+     * @param string $path optional path from the operation
822
+     *
823
+     * @throws StorageInvalidException if the storage is invalid, for example
824
+     * when the authentication expired or is invalid
825
+     * @throws StorageNotAvailableException if the storage is not available,
826
+     * which might be temporary
827
+     */
828
+    protected function convertException(Exception $e, $path = '') {
829
+        \OC::$server->getLogger()->logException($e, ['app' => 'files_external']);
830
+        if ($e instanceof ClientHttpException) {
831
+            if ($e->getHttpStatus() === Http::STATUS_LOCKED) {
832
+                throw new \OCP\Lock\LockedException($path);
833
+            }
834
+            if ($e->getHttpStatus() === Http::STATUS_UNAUTHORIZED) {
835
+                // either password was changed or was invalid all along
836
+                throw new StorageInvalidException(get_class($e) . ': ' . $e->getMessage());
837
+            } else if ($e->getHttpStatus() === Http::STATUS_METHOD_NOT_ALLOWED) {
838
+                // ignore exception for MethodNotAllowed, false will be returned
839
+                return;
840
+            }
841
+            throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
842
+        } else if ($e instanceof ClientException) {
843
+            // connection timeout or refused, server could be temporarily down
844
+            throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
845
+        } else if ($e instanceof \InvalidArgumentException) {
846
+            // parse error because the server returned HTML instead of XML,
847
+            // possibly temporarily down
848
+            throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
849
+        } else if (($e instanceof StorageNotAvailableException) || ($e instanceof StorageInvalidException)) {
850
+            // rethrow
851
+            throw $e;
852
+        }
853
+
854
+        // TODO: only log for now, but in the future need to wrap/rethrow exception
855
+    }
856 856
 }
857 857
 
Please login to merge, or discard this patch.
lib/private/Files/Storage/Temporary.php 2 patches
Indentation   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -29,20 +29,20 @@
 block discarded – undo
29 29
  * local storage backend in temporary folder for testing purpose
30 30
  */
31 31
 class Temporary extends Local{
32
-	public function __construct($arguments = null) {
33
-		parent::__construct(array('datadir' => \OC::$server->getTempManager()->getTemporaryFolder()));
34
-	}
32
+    public function __construct($arguments = null) {
33
+        parent::__construct(array('datadir' => \OC::$server->getTempManager()->getTemporaryFolder()));
34
+    }
35 35
 
36
-	public function cleanUp() {
37
-		\OC_Helper::rmdirr($this->datadir);
38
-	}
36
+    public function cleanUp() {
37
+        \OC_Helper::rmdirr($this->datadir);
38
+    }
39 39
 
40
-	public function __destruct() {
41
-		parent::__destruct();
42
-		$this->cleanUp();
43
-	}
40
+    public function __destruct() {
41
+        parent::__destruct();
42
+        $this->cleanUp();
43
+    }
44 44
 
45
-	public function getDataDir() {
46
-		return $this->datadir;
47
-	}
45
+    public function getDataDir() {
46
+        return $this->datadir;
47
+    }
48 48
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -28,7 +28,7 @@
 block discarded – undo
28 28
 /**
29 29
  * local storage backend in temporary folder for testing purpose
30 30
  */
31
-class Temporary extends Local{
31
+class Temporary extends Local {
32 32
 	public function __construct($arguments = null) {
33 33
 		parent::__construct(array('datadir' => \OC::$server->getTempManager()->getTemporaryFolder()));
34 34
 	}
Please login to merge, or discard this patch.
lib/private/Files/Storage/Local.php 3 patches
Braces   +3 added lines, -2 removed lines patch added patch discarded remove patch
@@ -317,8 +317,9 @@
 block discarded – undo
317 317
 		$files = array();
318 318
 		$physicalDir = $this->getSourcePath($dir);
319 319
 		foreach (scandir($physicalDir) as $item) {
320
-			if (\OC\Files\Filesystem::isIgnoredDir($item))
321
-				continue;
320
+			if (\OC\Files\Filesystem::isIgnoredDir($item)) {
321
+							continue;
322
+			}
322 323
 			$physicalItem = $physicalDir . '/' . $item;
323 324
 
324 325
 			if (strstr(strtolower($item), strtolower($query)) !== false) {
Please login to merge, or discard this patch.
Spacing   +14 added lines, -14 removed lines patch added patch discarded remove patch
@@ -66,7 +66,7 @@  discard block
 block discarded – undo
66 66
 			$this->realDataDir = $this->datadir;
67 67
 		} else {
68 68
 			$realPath = realpath($this->datadir) ?: $this->datadir;
69
-			$this->realDataDir = rtrim($realPath, '/') . '/';
69
+			$this->realDataDir = rtrim($realPath, '/').'/';
70 70
 		}
71 71
 		if (substr($this->datadir, -1) !== '/') {
72 72
 			$this->datadir .= '/';
@@ -78,7 +78,7 @@  discard block
 block discarded – undo
78 78
 	}
79 79
 
80 80
 	public function getId() {
81
-		return 'local::' . $this->datadir;
81
+		return 'local::'.$this->datadir;
82 82
 	}
83 83
 
84 84
 	public function mkdir($path) {
@@ -236,17 +236,17 @@  discard block
 block discarded – undo
236 236
 		$dstParent = dirname($path2);
237 237
 
238 238
 		if (!$this->isUpdatable($srcParent)) {
239
-			\OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : ' . $srcParent, \OCP\Util::ERROR);
239
+			\OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : '.$srcParent, \OCP\Util::ERROR);
240 240
 			return false;
241 241
 		}
242 242
 
243 243
 		if (!$this->isUpdatable($dstParent)) {
244
-			\OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : ' . $dstParent, \OCP\Util::ERROR);
244
+			\OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : '.$dstParent, \OCP\Util::ERROR);
245 245
 			return false;
246 246
 		}
247 247
 
248 248
 		if (!$this->file_exists($path1)) {
249
-			\OCP\Util::writeLog('core', 'unable to rename, file does not exists : ' . $path1, \OCP\Util::ERROR);
249
+			\OCP\Util::writeLog('core', 'unable to rename, file does not exists : '.$path1, \OCP\Util::ERROR);
250 250
 			return false;
251 251
 		}
252 252
 
@@ -327,13 +327,13 @@  discard block
 block discarded – undo
327 327
 		foreach (scandir($physicalDir) as $item) {
328 328
 			if (\OC\Files\Filesystem::isIgnoredDir($item))
329 329
 				continue;
330
-			$physicalItem = $physicalDir . '/' . $item;
330
+			$physicalItem = $physicalDir.'/'.$item;
331 331
 
332 332
 			if (strstr(strtolower($item), strtolower($query)) !== false) {
333
-				$files[] = $dir . '/' . $item;
333
+				$files[] = $dir.'/'.$item;
334 334
 			}
335 335
 			if (is_dir($physicalItem)) {
336
-				$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
336
+				$files = array_merge($files, $this->searchInDir($query, $dir.'/'.$item));
337 337
 			}
338 338
 		}
339 339
 		return $files;
@@ -362,7 +362,7 @@  discard block
 block discarded – undo
362 362
 	 * @throws ForbiddenException
363 363
 	 */
364 364
 	public function getSourcePath($path) {
365
-		$fullPath = $this->datadir . $path;
365
+		$fullPath = $this->datadir.$path;
366 366
 		$currentPath = $path;
367 367
 		if ($this->allowSymlinks || $currentPath === '') {
368 368
 			return $fullPath;
@@ -374,10 +374,10 @@  discard block
 block discarded – undo
374 374
 			if ($currentPath === '' || $currentPath === '.') {
375 375
 				return $fullPath;
376 376
 			}
377
-			$realPath = realpath($this->datadir . $currentPath);
377
+			$realPath = realpath($this->datadir.$currentPath);
378 378
 		}
379 379
 		if ($realPath) {
380
-			$realPath = $realPath . '/';
380
+			$realPath = $realPath.'/';
381 381
 		}
382 382
 		if (substr($realPath, 0, $this->dataDirLength) === $this->realDataDir) {
383 383
 			return $fullPath;
@@ -404,9 +404,9 @@  discard block
 block discarded – undo
404 404
 		if ($this->is_file($path)) {
405 405
 			$stat = $this->stat($path);
406 406
 			return md5(
407
-				$stat['mtime'] .
408
-				$stat['ino'] .
409
-				$stat['dev'] .
407
+				$stat['mtime'].
408
+				$stat['ino'].
409
+				$stat['dev'].
410 410
 				$stat['size']
411 411
 			);
412 412
 		} else {
Please login to merge, or discard this patch.
Indentation   +414 added lines, -414 removed lines patch added patch discarded remove patch
@@ -48,418 +48,418 @@
 block discarded – undo
48 48
  * for local filestore, we only have to map the paths
49 49
  */
50 50
 class Local extends \OC\Files\Storage\Common {
51
-	protected $datadir;
52
-
53
-	protected $dataDirLength;
54
-
55
-	protected $allowSymlinks = false;
56
-
57
-	protected $realDataDir;
58
-
59
-	public function __construct($arguments) {
60
-		if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) {
61
-			throw new \InvalidArgumentException('No data directory set for local storage');
62
-		}
63
-		$this->datadir = str_replace('//', '/', $arguments['datadir']);
64
-		// some crazy code uses a local storage on root...
65
-		if ($this->datadir === '/') {
66
-			$this->realDataDir = $this->datadir;
67
-		} else {
68
-			$realPath = realpath($this->datadir) ?: $this->datadir;
69
-			$this->realDataDir = rtrim($realPath, '/') . '/';
70
-		}
71
-		if (substr($this->datadir, -1) !== '/') {
72
-			$this->datadir .= '/';
73
-		}
74
-		$this->dataDirLength = strlen($this->realDataDir);
75
-	}
76
-
77
-	public function __destruct() {
78
-	}
79
-
80
-	public function getId() {
81
-		return 'local::' . $this->datadir;
82
-	}
83
-
84
-	public function mkdir($path) {
85
-		return @mkdir($this->getSourcePath($path), 0777, true);
86
-	}
87
-
88
-	public function rmdir($path) {
89
-		if (!$this->isDeletable($path)) {
90
-			return false;
91
-		}
92
-		try {
93
-			$it = new \RecursiveIteratorIterator(
94
-				new \RecursiveDirectoryIterator($this->getSourcePath($path)),
95
-				\RecursiveIteratorIterator::CHILD_FIRST
96
-			);
97
-			/**
98
-			 * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
99
-			 * This bug is fixed in PHP 5.5.9 or before
100
-			 * See #8376
101
-			 */
102
-			$it->rewind();
103
-			while ($it->valid()) {
104
-				/**
105
-				 * @var \SplFileInfo $file
106
-				 */
107
-				$file = $it->current();
108
-				if (in_array($file->getBasename(), array('.', '..'))) {
109
-					$it->next();
110
-					continue;
111
-				} elseif ($file->isDir()) {
112
-					rmdir($file->getPathname());
113
-				} elseif ($file->isFile() || $file->isLink()) {
114
-					unlink($file->getPathname());
115
-				}
116
-				$it->next();
117
-			}
118
-			return rmdir($this->getSourcePath($path));
119
-		} catch (\UnexpectedValueException $e) {
120
-			return false;
121
-		}
122
-	}
123
-
124
-	public function opendir($path) {
125
-		return opendir($this->getSourcePath($path));
126
-	}
127
-
128
-	public function is_dir($path) {
129
-		if (substr($path, -1) == '/') {
130
-			$path = substr($path, 0, -1);
131
-		}
132
-		return is_dir($this->getSourcePath($path));
133
-	}
134
-
135
-	public function is_file($path) {
136
-		return is_file($this->getSourcePath($path));
137
-	}
138
-
139
-	public function stat($path) {
140
-		clearstatcache();
141
-		$fullPath = $this->getSourcePath($path);
142
-		$statResult = stat($fullPath);
143
-		if (PHP_INT_SIZE === 4 && !$this->is_dir($path)) {
144
-			$filesize = $this->filesize($path);
145
-			$statResult['size'] = $filesize;
146
-			$statResult[7] = $filesize;
147
-		}
148
-		return $statResult;
149
-	}
150
-
151
-	public function filetype($path) {
152
-		$filetype = filetype($this->getSourcePath($path));
153
-		if ($filetype == 'link') {
154
-			$filetype = filetype(realpath($this->getSourcePath($path)));
155
-		}
156
-		return $filetype;
157
-	}
158
-
159
-	public function filesize($path) {
160
-		if ($this->is_dir($path)) {
161
-			return 0;
162
-		}
163
-		$fullPath = $this->getSourcePath($path);
164
-		if (PHP_INT_SIZE === 4) {
165
-			$helper = new \OC\LargeFileHelper;
166
-			return $helper->getFileSize($fullPath);
167
-		}
168
-		return filesize($fullPath);
169
-	}
170
-
171
-	public function isReadable($path) {
172
-		return is_readable($this->getSourcePath($path));
173
-	}
174
-
175
-	public function isUpdatable($path) {
176
-		return is_writable($this->getSourcePath($path));
177
-	}
178
-
179
-	public function file_exists($path) {
180
-		return file_exists($this->getSourcePath($path));
181
-	}
182
-
183
-	public function filemtime($path) {
184
-		$fullPath = $this->getSourcePath($path);
185
-		clearstatcache(true, $fullPath);
186
-		if (!$this->file_exists($path)) {
187
-			return false;
188
-		}
189
-		if (PHP_INT_SIZE === 4) {
190
-			$helper = new \OC\LargeFileHelper();
191
-			return $helper->getFileMtime($fullPath);
192
-		}
193
-		return filemtime($fullPath);
194
-	}
195
-
196
-	public function touch($path, $mtime = null) {
197
-		// sets the modification time of the file to the given value.
198
-		// If mtime is nil the current time is set.
199
-		// note that the access time of the file always changes to the current time.
200
-		if ($this->file_exists($path) and !$this->isUpdatable($path)) {
201
-			return false;
202
-		}
203
-		if (!is_null($mtime)) {
204
-			$result = touch($this->getSourcePath($path), $mtime);
205
-		} else {
206
-			$result = touch($this->getSourcePath($path));
207
-		}
208
-		if ($result) {
209
-			clearstatcache(true, $this->getSourcePath($path));
210
-		}
211
-
212
-		return $result;
213
-	}
214
-
215
-	public function file_get_contents($path) {
216
-		return file_get_contents($this->getSourcePath($path));
217
-	}
218
-
219
-	public function file_put_contents($path, $data) {
220
-		return file_put_contents($this->getSourcePath($path), $data);
221
-	}
222
-
223
-	public function unlink($path) {
224
-		if ($this->is_dir($path)) {
225
-			return $this->rmdir($path);
226
-		} else if ($this->is_file($path)) {
227
-			return unlink($this->getSourcePath($path));
228
-		} else {
229
-			return false;
230
-		}
231
-
232
-	}
233
-
234
-	public function rename($path1, $path2) {
235
-		$srcParent = dirname($path1);
236
-		$dstParent = dirname($path2);
237
-
238
-		if (!$this->isUpdatable($srcParent)) {
239
-			\OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : ' . $srcParent, \OCP\Util::ERROR);
240
-			return false;
241
-		}
242
-
243
-		if (!$this->isUpdatable($dstParent)) {
244
-			\OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : ' . $dstParent, \OCP\Util::ERROR);
245
-			return false;
246
-		}
247
-
248
-		if (!$this->file_exists($path1)) {
249
-			\OCP\Util::writeLog('core', 'unable to rename, file does not exists : ' . $path1, \OCP\Util::ERROR);
250
-			return false;
251
-		}
252
-
253
-		if ($this->is_dir($path2)) {
254
-			$this->rmdir($path2);
255
-		} else if ($this->is_file($path2)) {
256
-			$this->unlink($path2);
257
-		}
258
-
259
-		if ($this->is_dir($path1)) {
260
-			// we can't move folders across devices, use copy instead
261
-			$stat1 = stat(dirname($this->getSourcePath($path1)));
262
-			$stat2 = stat(dirname($this->getSourcePath($path2)));
263
-			if ($stat1['dev'] !== $stat2['dev']) {
264
-				$result = $this->copy($path1, $path2);
265
-				if ($result) {
266
-					$result &= $this->rmdir($path1);
267
-				}
268
-				return $result;
269
-			}
270
-		}
271
-
272
-		return rename($this->getSourcePath($path1), $this->getSourcePath($path2));
273
-	}
274
-
275
-	public function copy($path1, $path2) {
276
-		if ($this->is_dir($path1)) {
277
-			return parent::copy($path1, $path2);
278
-		} else {
279
-			return copy($this->getSourcePath($path1), $this->getSourcePath($path2));
280
-		}
281
-	}
282
-
283
-	public function fopen($path, $mode) {
284
-		return fopen($this->getSourcePath($path), $mode);
285
-	}
286
-
287
-	public function hash($type, $path, $raw = false) {
288
-		return hash_file($type, $this->getSourcePath($path), $raw);
289
-	}
290
-
291
-	public function free_space($path) {
292
-		$sourcePath = $this->getSourcePath($path);
293
-		// using !is_dir because $sourcePath might be a part file or
294
-		// non-existing file, so we'd still want to use the parent dir
295
-		// in such cases
296
-		if (!is_dir($sourcePath)) {
297
-			// disk_free_space doesn't work on files
298
-			$sourcePath = dirname($sourcePath);
299
-		}
300
-		$space = @disk_free_space($sourcePath);
301
-		if ($space === false || is_null($space)) {
302
-			return \OCP\Files\FileInfo::SPACE_UNKNOWN;
303
-		}
304
-		return $space;
305
-	}
306
-
307
-	public function search($query) {
308
-		return $this->searchInDir($query);
309
-	}
310
-
311
-	public function getLocalFile($path) {
312
-		return $this->getSourcePath($path);
313
-	}
314
-
315
-	public function getLocalFolder($path) {
316
-		return $this->getSourcePath($path);
317
-	}
318
-
319
-	/**
320
-	 * @param string $query
321
-	 * @param string $dir
322
-	 * @return array
323
-	 */
324
-	protected function searchInDir($query, $dir = '') {
325
-		$files = array();
326
-		$physicalDir = $this->getSourcePath($dir);
327
-		foreach (scandir($physicalDir) as $item) {
328
-			if (\OC\Files\Filesystem::isIgnoredDir($item))
329
-				continue;
330
-			$physicalItem = $physicalDir . '/' . $item;
331
-
332
-			if (strstr(strtolower($item), strtolower($query)) !== false) {
333
-				$files[] = $dir . '/' . $item;
334
-			}
335
-			if (is_dir($physicalItem)) {
336
-				$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
337
-			}
338
-		}
339
-		return $files;
340
-	}
341
-
342
-	/**
343
-	 * check if a file or folder has been updated since $time
344
-	 *
345
-	 * @param string $path
346
-	 * @param int $time
347
-	 * @return bool
348
-	 */
349
-	public function hasUpdated($path, $time) {
350
-		if ($this->file_exists($path)) {
351
-			return $this->filemtime($path) > $time;
352
-		} else {
353
-			return true;
354
-		}
355
-	}
356
-
357
-	/**
358
-	 * Get the source path (on disk) of a given path
359
-	 *
360
-	 * @param string $path
361
-	 * @return string
362
-	 * @throws ForbiddenException
363
-	 */
364
-	public function getSourcePath($path) {
365
-		$fullPath = $this->datadir . $path;
366
-		$currentPath = $path;
367
-		if ($this->allowSymlinks || $currentPath === '') {
368
-			return $fullPath;
369
-		}
370
-		$pathToResolve = $fullPath;
371
-		$realPath = realpath($pathToResolve);
372
-		while ($realPath === false) { // for non existing files check the parent directory
373
-			$currentPath = dirname($currentPath);
374
-			if ($currentPath === '' || $currentPath === '.') {
375
-				return $fullPath;
376
-			}
377
-			$realPath = realpath($this->datadir . $currentPath);
378
-		}
379
-		if ($realPath) {
380
-			$realPath = $realPath . '/';
381
-		}
382
-		if (substr($realPath, 0, $this->dataDirLength) === $this->realDataDir) {
383
-			return $fullPath;
384
-		}
385
-
386
-		\OCP\Util::writeLog('core', "Following symlinks is not allowed ('$fullPath' -> '$realPath' not inside '{$this->realDataDir}')", \OCP\Util::ERROR);
387
-		throw new ForbiddenException('Following symlinks is not allowed', false);
388
-	}
389
-
390
-	/**
391
-	 * {@inheritdoc}
392
-	 */
393
-	public function isLocal() {
394
-		return true;
395
-	}
396
-
397
-	/**
398
-	 * get the ETag for a file or folder
399
-	 *
400
-	 * @param string $path
401
-	 * @return string
402
-	 */
403
-	public function getETag($path) {
404
-		if ($this->is_file($path)) {
405
-			$stat = $this->stat($path);
406
-			return md5(
407
-				$stat['mtime'] .
408
-				$stat['ino'] .
409
-				$stat['dev'] .
410
-				$stat['size']
411
-			);
412
-		} else {
413
-			return parent::getETag($path);
414
-		}
415
-	}
416
-
417
-	/**
418
-	 * @param IStorage $sourceStorage
419
-	 * @param string $sourceInternalPath
420
-	 * @param string $targetInternalPath
421
-	 * @param bool $preserveMtime
422
-	 * @return bool
423
-	 */
424
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
425
-		if ($sourceStorage->instanceOfStorage(Local::class)) {
426
-			if ($sourceStorage->instanceOfStorage(Jail::class)) {
427
-				/**
428
-				 * @var \OC\Files\Storage\Wrapper\Jail $sourceStorage
429
-				 */
430
-				$sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
431
-			}
432
-			/**
433
-			 * @var \OC\Files\Storage\Local $sourceStorage
434
-			 */
435
-			$rootStorage = new Local(['datadir' => '/']);
436
-			return $rootStorage->copy($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
437
-		} else {
438
-			return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
439
-		}
440
-	}
441
-
442
-	/**
443
-	 * @param IStorage $sourceStorage
444
-	 * @param string $sourceInternalPath
445
-	 * @param string $targetInternalPath
446
-	 * @return bool
447
-	 */
448
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
449
-		if ($sourceStorage->instanceOfStorage(Local::class)) {
450
-			if ($sourceStorage->instanceOfStorage(Jail::class)) {
451
-				/**
452
-				 * @var \OC\Files\Storage\Wrapper\Jail $sourceStorage
453
-				 */
454
-				$sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
455
-			}
456
-			/**
457
-			 * @var \OC\Files\Storage\Local $sourceStorage
458
-			 */
459
-			$rootStorage = new Local(['datadir' => '/']);
460
-			return $rootStorage->rename($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
461
-		} else {
462
-			return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
463
-		}
464
-	}
51
+    protected $datadir;
52
+
53
+    protected $dataDirLength;
54
+
55
+    protected $allowSymlinks = false;
56
+
57
+    protected $realDataDir;
58
+
59
+    public function __construct($arguments) {
60
+        if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) {
61
+            throw new \InvalidArgumentException('No data directory set for local storage');
62
+        }
63
+        $this->datadir = str_replace('//', '/', $arguments['datadir']);
64
+        // some crazy code uses a local storage on root...
65
+        if ($this->datadir === '/') {
66
+            $this->realDataDir = $this->datadir;
67
+        } else {
68
+            $realPath = realpath($this->datadir) ?: $this->datadir;
69
+            $this->realDataDir = rtrim($realPath, '/') . '/';
70
+        }
71
+        if (substr($this->datadir, -1) !== '/') {
72
+            $this->datadir .= '/';
73
+        }
74
+        $this->dataDirLength = strlen($this->realDataDir);
75
+    }
76
+
77
+    public function __destruct() {
78
+    }
79
+
80
+    public function getId() {
81
+        return 'local::' . $this->datadir;
82
+    }
83
+
84
+    public function mkdir($path) {
85
+        return @mkdir($this->getSourcePath($path), 0777, true);
86
+    }
87
+
88
+    public function rmdir($path) {
89
+        if (!$this->isDeletable($path)) {
90
+            return false;
91
+        }
92
+        try {
93
+            $it = new \RecursiveIteratorIterator(
94
+                new \RecursiveDirectoryIterator($this->getSourcePath($path)),
95
+                \RecursiveIteratorIterator::CHILD_FIRST
96
+            );
97
+            /**
98
+             * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
99
+             * This bug is fixed in PHP 5.5.9 or before
100
+             * See #8376
101
+             */
102
+            $it->rewind();
103
+            while ($it->valid()) {
104
+                /**
105
+                 * @var \SplFileInfo $file
106
+                 */
107
+                $file = $it->current();
108
+                if (in_array($file->getBasename(), array('.', '..'))) {
109
+                    $it->next();
110
+                    continue;
111
+                } elseif ($file->isDir()) {
112
+                    rmdir($file->getPathname());
113
+                } elseif ($file->isFile() || $file->isLink()) {
114
+                    unlink($file->getPathname());
115
+                }
116
+                $it->next();
117
+            }
118
+            return rmdir($this->getSourcePath($path));
119
+        } catch (\UnexpectedValueException $e) {
120
+            return false;
121
+        }
122
+    }
123
+
124
+    public function opendir($path) {
125
+        return opendir($this->getSourcePath($path));
126
+    }
127
+
128
+    public function is_dir($path) {
129
+        if (substr($path, -1) == '/') {
130
+            $path = substr($path, 0, -1);
131
+        }
132
+        return is_dir($this->getSourcePath($path));
133
+    }
134
+
135
+    public function is_file($path) {
136
+        return is_file($this->getSourcePath($path));
137
+    }
138
+
139
+    public function stat($path) {
140
+        clearstatcache();
141
+        $fullPath = $this->getSourcePath($path);
142
+        $statResult = stat($fullPath);
143
+        if (PHP_INT_SIZE === 4 && !$this->is_dir($path)) {
144
+            $filesize = $this->filesize($path);
145
+            $statResult['size'] = $filesize;
146
+            $statResult[7] = $filesize;
147
+        }
148
+        return $statResult;
149
+    }
150
+
151
+    public function filetype($path) {
152
+        $filetype = filetype($this->getSourcePath($path));
153
+        if ($filetype == 'link') {
154
+            $filetype = filetype(realpath($this->getSourcePath($path)));
155
+        }
156
+        return $filetype;
157
+    }
158
+
159
+    public function filesize($path) {
160
+        if ($this->is_dir($path)) {
161
+            return 0;
162
+        }
163
+        $fullPath = $this->getSourcePath($path);
164
+        if (PHP_INT_SIZE === 4) {
165
+            $helper = new \OC\LargeFileHelper;
166
+            return $helper->getFileSize($fullPath);
167
+        }
168
+        return filesize($fullPath);
169
+    }
170
+
171
+    public function isReadable($path) {
172
+        return is_readable($this->getSourcePath($path));
173
+    }
174
+
175
+    public function isUpdatable($path) {
176
+        return is_writable($this->getSourcePath($path));
177
+    }
178
+
179
+    public function file_exists($path) {
180
+        return file_exists($this->getSourcePath($path));
181
+    }
182
+
183
+    public function filemtime($path) {
184
+        $fullPath = $this->getSourcePath($path);
185
+        clearstatcache(true, $fullPath);
186
+        if (!$this->file_exists($path)) {
187
+            return false;
188
+        }
189
+        if (PHP_INT_SIZE === 4) {
190
+            $helper = new \OC\LargeFileHelper();
191
+            return $helper->getFileMtime($fullPath);
192
+        }
193
+        return filemtime($fullPath);
194
+    }
195
+
196
+    public function touch($path, $mtime = null) {
197
+        // sets the modification time of the file to the given value.
198
+        // If mtime is nil the current time is set.
199
+        // note that the access time of the file always changes to the current time.
200
+        if ($this->file_exists($path) and !$this->isUpdatable($path)) {
201
+            return false;
202
+        }
203
+        if (!is_null($mtime)) {
204
+            $result = touch($this->getSourcePath($path), $mtime);
205
+        } else {
206
+            $result = touch($this->getSourcePath($path));
207
+        }
208
+        if ($result) {
209
+            clearstatcache(true, $this->getSourcePath($path));
210
+        }
211
+
212
+        return $result;
213
+    }
214
+
215
+    public function file_get_contents($path) {
216
+        return file_get_contents($this->getSourcePath($path));
217
+    }
218
+
219
+    public function file_put_contents($path, $data) {
220
+        return file_put_contents($this->getSourcePath($path), $data);
221
+    }
222
+
223
+    public function unlink($path) {
224
+        if ($this->is_dir($path)) {
225
+            return $this->rmdir($path);
226
+        } else if ($this->is_file($path)) {
227
+            return unlink($this->getSourcePath($path));
228
+        } else {
229
+            return false;
230
+        }
231
+
232
+    }
233
+
234
+    public function rename($path1, $path2) {
235
+        $srcParent = dirname($path1);
236
+        $dstParent = dirname($path2);
237
+
238
+        if (!$this->isUpdatable($srcParent)) {
239
+            \OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : ' . $srcParent, \OCP\Util::ERROR);
240
+            return false;
241
+        }
242
+
243
+        if (!$this->isUpdatable($dstParent)) {
244
+            \OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : ' . $dstParent, \OCP\Util::ERROR);
245
+            return false;
246
+        }
247
+
248
+        if (!$this->file_exists($path1)) {
249
+            \OCP\Util::writeLog('core', 'unable to rename, file does not exists : ' . $path1, \OCP\Util::ERROR);
250
+            return false;
251
+        }
252
+
253
+        if ($this->is_dir($path2)) {
254
+            $this->rmdir($path2);
255
+        } else if ($this->is_file($path2)) {
256
+            $this->unlink($path2);
257
+        }
258
+
259
+        if ($this->is_dir($path1)) {
260
+            // we can't move folders across devices, use copy instead
261
+            $stat1 = stat(dirname($this->getSourcePath($path1)));
262
+            $stat2 = stat(dirname($this->getSourcePath($path2)));
263
+            if ($stat1['dev'] !== $stat2['dev']) {
264
+                $result = $this->copy($path1, $path2);
265
+                if ($result) {
266
+                    $result &= $this->rmdir($path1);
267
+                }
268
+                return $result;
269
+            }
270
+        }
271
+
272
+        return rename($this->getSourcePath($path1), $this->getSourcePath($path2));
273
+    }
274
+
275
+    public function copy($path1, $path2) {
276
+        if ($this->is_dir($path1)) {
277
+            return parent::copy($path1, $path2);
278
+        } else {
279
+            return copy($this->getSourcePath($path1), $this->getSourcePath($path2));
280
+        }
281
+    }
282
+
283
+    public function fopen($path, $mode) {
284
+        return fopen($this->getSourcePath($path), $mode);
285
+    }
286
+
287
+    public function hash($type, $path, $raw = false) {
288
+        return hash_file($type, $this->getSourcePath($path), $raw);
289
+    }
290
+
291
+    public function free_space($path) {
292
+        $sourcePath = $this->getSourcePath($path);
293
+        // using !is_dir because $sourcePath might be a part file or
294
+        // non-existing file, so we'd still want to use the parent dir
295
+        // in such cases
296
+        if (!is_dir($sourcePath)) {
297
+            // disk_free_space doesn't work on files
298
+            $sourcePath = dirname($sourcePath);
299
+        }
300
+        $space = @disk_free_space($sourcePath);
301
+        if ($space === false || is_null($space)) {
302
+            return \OCP\Files\FileInfo::SPACE_UNKNOWN;
303
+        }
304
+        return $space;
305
+    }
306
+
307
+    public function search($query) {
308
+        return $this->searchInDir($query);
309
+    }
310
+
311
+    public function getLocalFile($path) {
312
+        return $this->getSourcePath($path);
313
+    }
314
+
315
+    public function getLocalFolder($path) {
316
+        return $this->getSourcePath($path);
317
+    }
318
+
319
+    /**
320
+     * @param string $query
321
+     * @param string $dir
322
+     * @return array
323
+     */
324
+    protected function searchInDir($query, $dir = '') {
325
+        $files = array();
326
+        $physicalDir = $this->getSourcePath($dir);
327
+        foreach (scandir($physicalDir) as $item) {
328
+            if (\OC\Files\Filesystem::isIgnoredDir($item))
329
+                continue;
330
+            $physicalItem = $physicalDir . '/' . $item;
331
+
332
+            if (strstr(strtolower($item), strtolower($query)) !== false) {
333
+                $files[] = $dir . '/' . $item;
334
+            }
335
+            if (is_dir($physicalItem)) {
336
+                $files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
337
+            }
338
+        }
339
+        return $files;
340
+    }
341
+
342
+    /**
343
+     * check if a file or folder has been updated since $time
344
+     *
345
+     * @param string $path
346
+     * @param int $time
347
+     * @return bool
348
+     */
349
+    public function hasUpdated($path, $time) {
350
+        if ($this->file_exists($path)) {
351
+            return $this->filemtime($path) > $time;
352
+        } else {
353
+            return true;
354
+        }
355
+    }
356
+
357
+    /**
358
+     * Get the source path (on disk) of a given path
359
+     *
360
+     * @param string $path
361
+     * @return string
362
+     * @throws ForbiddenException
363
+     */
364
+    public function getSourcePath($path) {
365
+        $fullPath = $this->datadir . $path;
366
+        $currentPath = $path;
367
+        if ($this->allowSymlinks || $currentPath === '') {
368
+            return $fullPath;
369
+        }
370
+        $pathToResolve = $fullPath;
371
+        $realPath = realpath($pathToResolve);
372
+        while ($realPath === false) { // for non existing files check the parent directory
373
+            $currentPath = dirname($currentPath);
374
+            if ($currentPath === '' || $currentPath === '.') {
375
+                return $fullPath;
376
+            }
377
+            $realPath = realpath($this->datadir . $currentPath);
378
+        }
379
+        if ($realPath) {
380
+            $realPath = $realPath . '/';
381
+        }
382
+        if (substr($realPath, 0, $this->dataDirLength) === $this->realDataDir) {
383
+            return $fullPath;
384
+        }
385
+
386
+        \OCP\Util::writeLog('core', "Following symlinks is not allowed ('$fullPath' -> '$realPath' not inside '{$this->realDataDir}')", \OCP\Util::ERROR);
387
+        throw new ForbiddenException('Following symlinks is not allowed', false);
388
+    }
389
+
390
+    /**
391
+     * {@inheritdoc}
392
+     */
393
+    public function isLocal() {
394
+        return true;
395
+    }
396
+
397
+    /**
398
+     * get the ETag for a file or folder
399
+     *
400
+     * @param string $path
401
+     * @return string
402
+     */
403
+    public function getETag($path) {
404
+        if ($this->is_file($path)) {
405
+            $stat = $this->stat($path);
406
+            return md5(
407
+                $stat['mtime'] .
408
+                $stat['ino'] .
409
+                $stat['dev'] .
410
+                $stat['size']
411
+            );
412
+        } else {
413
+            return parent::getETag($path);
414
+        }
415
+    }
416
+
417
+    /**
418
+     * @param IStorage $sourceStorage
419
+     * @param string $sourceInternalPath
420
+     * @param string $targetInternalPath
421
+     * @param bool $preserveMtime
422
+     * @return bool
423
+     */
424
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
425
+        if ($sourceStorage->instanceOfStorage(Local::class)) {
426
+            if ($sourceStorage->instanceOfStorage(Jail::class)) {
427
+                /**
428
+                 * @var \OC\Files\Storage\Wrapper\Jail $sourceStorage
429
+                 */
430
+                $sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
431
+            }
432
+            /**
433
+             * @var \OC\Files\Storage\Local $sourceStorage
434
+             */
435
+            $rootStorage = new Local(['datadir' => '/']);
436
+            return $rootStorage->copy($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
437
+        } else {
438
+            return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
439
+        }
440
+    }
441
+
442
+    /**
443
+     * @param IStorage $sourceStorage
444
+     * @param string $sourceInternalPath
445
+     * @param string $targetInternalPath
446
+     * @return bool
447
+     */
448
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
449
+        if ($sourceStorage->instanceOfStorage(Local::class)) {
450
+            if ($sourceStorage->instanceOfStorage(Jail::class)) {
451
+                /**
452
+                 * @var \OC\Files\Storage\Wrapper\Jail $sourceStorage
453
+                 */
454
+                $sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
455
+            }
456
+            /**
457
+             * @var \OC\Files\Storage\Local $sourceStorage
458
+             */
459
+            $rootStorage = new Local(['datadir' => '/']);
460
+            return $rootStorage->rename($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
461
+        } else {
462
+            return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
463
+        }
464
+    }
465 465
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/LocalTempFileTrait.php 1 patch
Indentation   +38 added lines, -38 removed lines patch added patch discarded remove patch
@@ -37,45 +37,45 @@
 block discarded – undo
37 37
  */
38 38
 trait LocalTempFileTrait {
39 39
 
40
-	/** @var string[] */
41
-	protected $cachedFiles = [];
40
+    /** @var string[] */
41
+    protected $cachedFiles = [];
42 42
 
43
-	/**
44
-	 * @param string $path
45
-	 * @return string
46
-	 */
47
-	protected function getCachedFile($path) {
48
-		if (!isset($this->cachedFiles[$path])) {
49
-			$this->cachedFiles[$path] = $this->toTmpFile($path);
50
-		}
51
-		return $this->cachedFiles[$path];
52
-	}
43
+    /**
44
+     * @param string $path
45
+     * @return string
46
+     */
47
+    protected function getCachedFile($path) {
48
+        if (!isset($this->cachedFiles[$path])) {
49
+            $this->cachedFiles[$path] = $this->toTmpFile($path);
50
+        }
51
+        return $this->cachedFiles[$path];
52
+    }
53 53
 
54
-	/**
55
-	 * @param string $path
56
-	 */
57
-	protected function removeCachedFile($path) {
58
-		unset($this->cachedFiles[$path]);
59
-	}
54
+    /**
55
+     * @param string $path
56
+     */
57
+    protected function removeCachedFile($path) {
58
+        unset($this->cachedFiles[$path]);
59
+    }
60 60
 
61
-	/**
62
-	 * @param string $path
63
-	 * @return string
64
-	 */
65
-	protected function toTmpFile($path) { //no longer in the storage api, still useful here
66
-		$source = $this->fopen($path, 'r');
67
-		if (!$source) {
68
-			return false;
69
-		}
70
-		if ($pos = strrpos($path, '.')) {
71
-			$extension = substr($path, $pos);
72
-		} else {
73
-			$extension = '';
74
-		}
75
-		$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($extension);
76
-		$target = fopen($tmpFile, 'w');
77
-		\OC_Helper::streamCopy($source, $target);
78
-		fclose($target);
79
-		return $tmpFile;
80
-	}
61
+    /**
62
+     * @param string $path
63
+     * @return string
64
+     */
65
+    protected function toTmpFile($path) { //no longer in the storage api, still useful here
66
+        $source = $this->fopen($path, 'r');
67
+        if (!$source) {
68
+            return false;
69
+        }
70
+        if ($pos = strrpos($path, '.')) {
71
+            $extension = substr($path, $pos);
72
+        } else {
73
+            $extension = '';
74
+        }
75
+        $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($extension);
76
+        $target = fopen($tmpFile, 'w');
77
+        \OC_Helper::streamCopy($source, $target);
78
+        fclose($target);
79
+        return $tmpFile;
80
+    }
81 81
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/Common.php 3 patches
Braces   +3 added lines, -1 removed lines patch added patch discarded remove patch
@@ -289,7 +289,9 @@
 block discarded – undo
289 289
 		$dh = $this->opendir($dir);
290 290
 		if (is_resource($dh)) {
291 291
 			while (($item = readdir($dh)) !== false) {
292
-				if (\OC\Files\Filesystem::isIgnoredDir($item)) continue;
292
+				if (\OC\Files\Filesystem::isIgnoredDir($item)) {
293
+				    continue;
294
+				}
293 295
 				if (strstr(strtolower($item), strtolower($query)) !== false) {
294 296
 					$files[] = $dir . '/' . $item;
295 297
 				}
Please login to merge, or discard this patch.
Indentation   +736 added lines, -736 removed lines patch added patch discarded remove patch
@@ -70,744 +70,744 @@
 block discarded – undo
70 70
  */
71 71
 abstract class Common implements Storage, ILockingStorage {
72 72
 
73
-	use LocalTempFileTrait;
74
-
75
-	protected $cache;
76
-	protected $scanner;
77
-	protected $watcher;
78
-	protected $propagator;
79
-	protected $storageCache;
80
-	protected $updater;
81
-
82
-	protected $mountOptions = [];
83
-	protected $owner = null;
84
-
85
-	private $shouldLogLocks = null;
86
-	private $logger;
87
-
88
-	public function __construct($parameters) {
89
-	}
90
-
91
-	/**
92
-	 * Remove a file or folder
93
-	 *
94
-	 * @param string $path
95
-	 * @return bool
96
-	 */
97
-	protected function remove($path) {
98
-		if ($this->is_dir($path)) {
99
-			return $this->rmdir($path);
100
-		} else if ($this->is_file($path)) {
101
-			return $this->unlink($path);
102
-		} else {
103
-			return false;
104
-		}
105
-	}
106
-
107
-	public function is_dir($path) {
108
-		return $this->filetype($path) === 'dir';
109
-	}
110
-
111
-	public function is_file($path) {
112
-		return $this->filetype($path) === 'file';
113
-	}
114
-
115
-	public function filesize($path) {
116
-		if ($this->is_dir($path)) {
117
-			return 0; //by definition
118
-		} else {
119
-			$stat = $this->stat($path);
120
-			if (isset($stat['size'])) {
121
-				return $stat['size'];
122
-			} else {
123
-				return 0;
124
-			}
125
-		}
126
-	}
127
-
128
-	public function isReadable($path) {
129
-		// at least check whether it exists
130
-		// subclasses might want to implement this more thoroughly
131
-		return $this->file_exists($path);
132
-	}
133
-
134
-	public function isUpdatable($path) {
135
-		// at least check whether it exists
136
-		// subclasses might want to implement this more thoroughly
137
-		// a non-existing file/folder isn't updatable
138
-		return $this->file_exists($path);
139
-	}
140
-
141
-	public function isCreatable($path) {
142
-		if ($this->is_dir($path) && $this->isUpdatable($path)) {
143
-			return true;
144
-		}
145
-		return false;
146
-	}
147
-
148
-	public function isDeletable($path) {
149
-		if ($path === '' || $path === '/') {
150
-			return false;
151
-		}
152
-		$parent = dirname($path);
153
-		return $this->isUpdatable($parent) && $this->isUpdatable($path);
154
-	}
155
-
156
-	public function isSharable($path) {
157
-		return $this->isReadable($path);
158
-	}
159
-
160
-	public function getPermissions($path) {
161
-		$permissions = 0;
162
-		if ($this->isCreatable($path)) {
163
-			$permissions |= \OCP\Constants::PERMISSION_CREATE;
164
-		}
165
-		if ($this->isReadable($path)) {
166
-			$permissions |= \OCP\Constants::PERMISSION_READ;
167
-		}
168
-		if ($this->isUpdatable($path)) {
169
-			$permissions |= \OCP\Constants::PERMISSION_UPDATE;
170
-		}
171
-		if ($this->isDeletable($path)) {
172
-			$permissions |= \OCP\Constants::PERMISSION_DELETE;
173
-		}
174
-		if ($this->isSharable($path)) {
175
-			$permissions |= \OCP\Constants::PERMISSION_SHARE;
176
-		}
177
-		return $permissions;
178
-	}
179
-
180
-	public function filemtime($path) {
181
-		$stat = $this->stat($path);
182
-		if (isset($stat['mtime']) && $stat['mtime'] > 0) {
183
-			return $stat['mtime'];
184
-		} else {
185
-			return 0;
186
-		}
187
-	}
188
-
189
-	public function file_get_contents($path) {
190
-		$handle = $this->fopen($path, "r");
191
-		if (!$handle) {
192
-			return false;
193
-		}
194
-		$data = stream_get_contents($handle);
195
-		fclose($handle);
196
-		return $data;
197
-	}
198
-
199
-	public function file_put_contents($path, $data) {
200
-		$handle = $this->fopen($path, "w");
201
-		$this->removeCachedFile($path);
202
-		$count = fwrite($handle, $data);
203
-		fclose($handle);
204
-		return $count;
205
-	}
206
-
207
-	public function rename($path1, $path2) {
208
-		$this->remove($path2);
209
-
210
-		$this->removeCachedFile($path1);
211
-		return $this->copy($path1, $path2) and $this->remove($path1);
212
-	}
213
-
214
-	public function copy($path1, $path2) {
215
-		if ($this->is_dir($path1)) {
216
-			$this->remove($path2);
217
-			$dir = $this->opendir($path1);
218
-			$this->mkdir($path2);
219
-			while ($file = readdir($dir)) {
220
-				if (!Filesystem::isIgnoredDir($file)) {
221
-					if (!$this->copy($path1 . '/' . $file, $path2 . '/' . $file)) {
222
-						return false;
223
-					}
224
-				}
225
-			}
226
-			closedir($dir);
227
-			return true;
228
-		} else {
229
-			$source = $this->fopen($path1, 'r');
230
-			$target = $this->fopen($path2, 'w');
231
-			list(, $result) = \OC_Helper::streamCopy($source, $target);
232
-			if (!$result) {
233
-				\OC::$server->getLogger()->warning("Failed to write data while copying $path1 to $path2");
234
-			}
235
-			$this->removeCachedFile($path2);
236
-			return $result;
237
-		}
238
-	}
239
-
240
-	public function getMimeType($path) {
241
-		if ($this->is_dir($path)) {
242
-			return 'httpd/unix-directory';
243
-		} elseif ($this->file_exists($path)) {
244
-			return \OC::$server->getMimeTypeDetector()->detectPath($path);
245
-		} else {
246
-			return false;
247
-		}
248
-	}
249
-
250
-	public function hash($type, $path, $raw = false) {
251
-		$fh = $this->fopen($path, 'rb');
252
-		$ctx = hash_init($type);
253
-		hash_update_stream($ctx, $fh);
254
-		fclose($fh);
255
-		return hash_final($ctx, $raw);
256
-	}
257
-
258
-	public function search($query) {
259
-		return $this->searchInDir($query);
260
-	}
261
-
262
-	public function getLocalFile($path) {
263
-		return $this->getCachedFile($path);
264
-	}
265
-
266
-	/**
267
-	 * @param string $path
268
-	 * @param string $target
269
-	 */
270
-	private function addLocalFolder($path, $target) {
271
-		$dh = $this->opendir($path);
272
-		if (is_resource($dh)) {
273
-			while (($file = readdir($dh)) !== false) {
274
-				if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
275
-					if ($this->is_dir($path . '/' . $file)) {
276
-						mkdir($target . '/' . $file);
277
-						$this->addLocalFolder($path . '/' . $file, $target . '/' . $file);
278
-					} else {
279
-						$tmp = $this->toTmpFile($path . '/' . $file);
280
-						rename($tmp, $target . '/' . $file);
281
-					}
282
-				}
283
-			}
284
-		}
285
-	}
286
-
287
-	/**
288
-	 * @param string $query
289
-	 * @param string $dir
290
-	 * @return array
291
-	 */
292
-	protected function searchInDir($query, $dir = '') {
293
-		$files = array();
294
-		$dh = $this->opendir($dir);
295
-		if (is_resource($dh)) {
296
-			while (($item = readdir($dh)) !== false) {
297
-				if (\OC\Files\Filesystem::isIgnoredDir($item)) continue;
298
-				if (strstr(strtolower($item), strtolower($query)) !== false) {
299
-					$files[] = $dir . '/' . $item;
300
-				}
301
-				if ($this->is_dir($dir . '/' . $item)) {
302
-					$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
303
-				}
304
-			}
305
-		}
306
-		closedir($dh);
307
-		return $files;
308
-	}
309
-
310
-	/**
311
-	 * check if a file or folder has been updated since $time
312
-	 *
313
-	 * The method is only used to check if the cache needs to be updated. Storage backends that don't support checking
314
-	 * the mtime should always return false here. As a result storage implementations that always return false expect
315
-	 * exclusive access to the backend and will not pick up files that have been added in a way that circumvents
316
-	 * ownClouds filesystem.
317
-	 *
318
-	 * @param string $path
319
-	 * @param int $time
320
-	 * @return bool
321
-	 */
322
-	public function hasUpdated($path, $time) {
323
-		return $this->filemtime($path) > $time;
324
-	}
325
-
326
-	public function getCache($path = '', $storage = null) {
327
-		if (!$storage) {
328
-			$storage = $this;
329
-		}
330
-		if (!isset($storage->cache)) {
331
-			$storage->cache = new Cache($storage);
332
-		}
333
-		return $storage->cache;
334
-	}
335
-
336
-	public function getScanner($path = '', $storage = null) {
337
-		if (!$storage) {
338
-			$storage = $this;
339
-		}
340
-		if (!isset($storage->scanner)) {
341
-			$storage->scanner = new Scanner($storage);
342
-		}
343
-		return $storage->scanner;
344
-	}
345
-
346
-	public function getWatcher($path = '', $storage = null) {
347
-		if (!$storage) {
348
-			$storage = $this;
349
-		}
350
-		if (!isset($this->watcher)) {
351
-			$this->watcher = new Watcher($storage);
352
-			$globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER);
353
-			$this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy));
354
-		}
355
-		return $this->watcher;
356
-	}
357
-
358
-	/**
359
-	 * get a propagator instance for the cache
360
-	 *
361
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
362
-	 * @return \OC\Files\Cache\Propagator
363
-	 */
364
-	public function getPropagator($storage = null) {
365
-		if (!$storage) {
366
-			$storage = $this;
367
-		}
368
-		if (!isset($storage->propagator)) {
369
-			$storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection());
370
-		}
371
-		return $storage->propagator;
372
-	}
373
-
374
-	public function getUpdater($storage = null) {
375
-		if (!$storage) {
376
-			$storage = $this;
377
-		}
378
-		if (!isset($storage->updater)) {
379
-			$storage->updater = new Updater($storage);
380
-		}
381
-		return $storage->updater;
382
-	}
383
-
384
-	public function getStorageCache($storage = null) {
385
-		if (!$storage) {
386
-			$storage = $this;
387
-		}
388
-		if (!isset($this->storageCache)) {
389
-			$this->storageCache = new \OC\Files\Cache\Storage($storage);
390
-		}
391
-		return $this->storageCache;
392
-	}
393
-
394
-	/**
395
-	 * get the owner of a path
396
-	 *
397
-	 * @param string $path The path to get the owner
398
-	 * @return string|false uid or false
399
-	 */
400
-	public function getOwner($path) {
401
-		if ($this->owner === null) {
402
-			$this->owner = \OC_User::getUser();
403
-		}
404
-
405
-		return $this->owner;
406
-	}
407
-
408
-	/**
409
-	 * get the ETag for a file or folder
410
-	 *
411
-	 * @param string $path
412
-	 * @return string
413
-	 */
414
-	public function getETag($path) {
415
-		return uniqid();
416
-	}
417
-
418
-	/**
419
-	 * clean a path, i.e. remove all redundant '.' and '..'
420
-	 * making sure that it can't point to higher than '/'
421
-	 *
422
-	 * @param string $path The path to clean
423
-	 * @return string cleaned path
424
-	 */
425
-	public function cleanPath($path) {
426
-		if (strlen($path) == 0 or $path[0] != '/') {
427
-			$path = '/' . $path;
428
-		}
429
-
430
-		$output = array();
431
-		foreach (explode('/', $path) as $chunk) {
432
-			if ($chunk == '..') {
433
-				array_pop($output);
434
-			} else if ($chunk == '.') {
435
-			} else {
436
-				$output[] = $chunk;
437
-			}
438
-		}
439
-		return implode('/', $output);
440
-	}
441
-
442
-	/**
443
-	 * Test a storage for availability
444
-	 *
445
-	 * @return bool
446
-	 */
447
-	public function test() {
448
-		try {
449
-			if ($this->stat('')) {
450
-				return true;
451
-			}
452
-			\OC::$server->getLogger()->info("External storage not available: stat() failed");
453
-			return false;
454
-		} catch (\Exception $e) {
455
-			\OC::$server->getLogger()->info("External storage not available: " . $e->getMessage());
456
-			\OC::$server->getLogger()->logException($e, ['level' => \OCP\Util::DEBUG]);
457
-			return false;
458
-		}
459
-	}
460
-
461
-	/**
462
-	 * get the free space in the storage
463
-	 *
464
-	 * @param string $path
465
-	 * @return int|false
466
-	 */
467
-	public function free_space($path) {
468
-		return \OCP\Files\FileInfo::SPACE_UNKNOWN;
469
-	}
470
-
471
-	/**
472
-	 * {@inheritdoc}
473
-	 */
474
-	public function isLocal() {
475
-		// the common implementation returns a temporary file by
476
-		// default, which is not local
477
-		return false;
478
-	}
479
-
480
-	/**
481
-	 * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
482
-	 *
483
-	 * @param string $class
484
-	 * @return bool
485
-	 */
486
-	public function instanceOfStorage($class) {
487
-		if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
488
-			// FIXME Temporary fix to keep existing checks working
489
-			$class = '\OCA\Files_Sharing\SharedStorage';
490
-		}
491
-		return is_a($this, $class);
492
-	}
493
-
494
-	/**
495
-	 * A custom storage implementation can return an url for direct download of a give file.
496
-	 *
497
-	 * For now the returned array can hold the parameter url - in future more attributes might follow.
498
-	 *
499
-	 * @param string $path
500
-	 * @return array|false
501
-	 */
502
-	public function getDirectDownload($path) {
503
-		return [];
504
-	}
505
-
506
-	/**
507
-	 * @inheritdoc
508
-	 * @throws InvalidPathException
509
-	 */
510
-	public function verifyPath($path, $fileName) {
511
-
512
-		// verify empty and dot files
513
-		$trimmed = trim($fileName);
514
-		if ($trimmed === '') {
515
-			throw new EmptyFileNameException();
516
-		}
517
-
518
-		if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
519
-			throw new InvalidDirectoryException();
520
-		}
521
-
522
-		if (!\OC::$server->getDatabaseConnection()->supports4ByteText()) {
523
-			// verify database - e.g. mysql only 3-byte chars
524
-			if (preg_match('%(?:
73
+    use LocalTempFileTrait;
74
+
75
+    protected $cache;
76
+    protected $scanner;
77
+    protected $watcher;
78
+    protected $propagator;
79
+    protected $storageCache;
80
+    protected $updater;
81
+
82
+    protected $mountOptions = [];
83
+    protected $owner = null;
84
+
85
+    private $shouldLogLocks = null;
86
+    private $logger;
87
+
88
+    public function __construct($parameters) {
89
+    }
90
+
91
+    /**
92
+     * Remove a file or folder
93
+     *
94
+     * @param string $path
95
+     * @return bool
96
+     */
97
+    protected function remove($path) {
98
+        if ($this->is_dir($path)) {
99
+            return $this->rmdir($path);
100
+        } else if ($this->is_file($path)) {
101
+            return $this->unlink($path);
102
+        } else {
103
+            return false;
104
+        }
105
+    }
106
+
107
+    public function is_dir($path) {
108
+        return $this->filetype($path) === 'dir';
109
+    }
110
+
111
+    public function is_file($path) {
112
+        return $this->filetype($path) === 'file';
113
+    }
114
+
115
+    public function filesize($path) {
116
+        if ($this->is_dir($path)) {
117
+            return 0; //by definition
118
+        } else {
119
+            $stat = $this->stat($path);
120
+            if (isset($stat['size'])) {
121
+                return $stat['size'];
122
+            } else {
123
+                return 0;
124
+            }
125
+        }
126
+    }
127
+
128
+    public function isReadable($path) {
129
+        // at least check whether it exists
130
+        // subclasses might want to implement this more thoroughly
131
+        return $this->file_exists($path);
132
+    }
133
+
134
+    public function isUpdatable($path) {
135
+        // at least check whether it exists
136
+        // subclasses might want to implement this more thoroughly
137
+        // a non-existing file/folder isn't updatable
138
+        return $this->file_exists($path);
139
+    }
140
+
141
+    public function isCreatable($path) {
142
+        if ($this->is_dir($path) && $this->isUpdatable($path)) {
143
+            return true;
144
+        }
145
+        return false;
146
+    }
147
+
148
+    public function isDeletable($path) {
149
+        if ($path === '' || $path === '/') {
150
+            return false;
151
+        }
152
+        $parent = dirname($path);
153
+        return $this->isUpdatable($parent) && $this->isUpdatable($path);
154
+    }
155
+
156
+    public function isSharable($path) {
157
+        return $this->isReadable($path);
158
+    }
159
+
160
+    public function getPermissions($path) {
161
+        $permissions = 0;
162
+        if ($this->isCreatable($path)) {
163
+            $permissions |= \OCP\Constants::PERMISSION_CREATE;
164
+        }
165
+        if ($this->isReadable($path)) {
166
+            $permissions |= \OCP\Constants::PERMISSION_READ;
167
+        }
168
+        if ($this->isUpdatable($path)) {
169
+            $permissions |= \OCP\Constants::PERMISSION_UPDATE;
170
+        }
171
+        if ($this->isDeletable($path)) {
172
+            $permissions |= \OCP\Constants::PERMISSION_DELETE;
173
+        }
174
+        if ($this->isSharable($path)) {
175
+            $permissions |= \OCP\Constants::PERMISSION_SHARE;
176
+        }
177
+        return $permissions;
178
+    }
179
+
180
+    public function filemtime($path) {
181
+        $stat = $this->stat($path);
182
+        if (isset($stat['mtime']) && $stat['mtime'] > 0) {
183
+            return $stat['mtime'];
184
+        } else {
185
+            return 0;
186
+        }
187
+    }
188
+
189
+    public function file_get_contents($path) {
190
+        $handle = $this->fopen($path, "r");
191
+        if (!$handle) {
192
+            return false;
193
+        }
194
+        $data = stream_get_contents($handle);
195
+        fclose($handle);
196
+        return $data;
197
+    }
198
+
199
+    public function file_put_contents($path, $data) {
200
+        $handle = $this->fopen($path, "w");
201
+        $this->removeCachedFile($path);
202
+        $count = fwrite($handle, $data);
203
+        fclose($handle);
204
+        return $count;
205
+    }
206
+
207
+    public function rename($path1, $path2) {
208
+        $this->remove($path2);
209
+
210
+        $this->removeCachedFile($path1);
211
+        return $this->copy($path1, $path2) and $this->remove($path1);
212
+    }
213
+
214
+    public function copy($path1, $path2) {
215
+        if ($this->is_dir($path1)) {
216
+            $this->remove($path2);
217
+            $dir = $this->opendir($path1);
218
+            $this->mkdir($path2);
219
+            while ($file = readdir($dir)) {
220
+                if (!Filesystem::isIgnoredDir($file)) {
221
+                    if (!$this->copy($path1 . '/' . $file, $path2 . '/' . $file)) {
222
+                        return false;
223
+                    }
224
+                }
225
+            }
226
+            closedir($dir);
227
+            return true;
228
+        } else {
229
+            $source = $this->fopen($path1, 'r');
230
+            $target = $this->fopen($path2, 'w');
231
+            list(, $result) = \OC_Helper::streamCopy($source, $target);
232
+            if (!$result) {
233
+                \OC::$server->getLogger()->warning("Failed to write data while copying $path1 to $path2");
234
+            }
235
+            $this->removeCachedFile($path2);
236
+            return $result;
237
+        }
238
+    }
239
+
240
+    public function getMimeType($path) {
241
+        if ($this->is_dir($path)) {
242
+            return 'httpd/unix-directory';
243
+        } elseif ($this->file_exists($path)) {
244
+            return \OC::$server->getMimeTypeDetector()->detectPath($path);
245
+        } else {
246
+            return false;
247
+        }
248
+    }
249
+
250
+    public function hash($type, $path, $raw = false) {
251
+        $fh = $this->fopen($path, 'rb');
252
+        $ctx = hash_init($type);
253
+        hash_update_stream($ctx, $fh);
254
+        fclose($fh);
255
+        return hash_final($ctx, $raw);
256
+    }
257
+
258
+    public function search($query) {
259
+        return $this->searchInDir($query);
260
+    }
261
+
262
+    public function getLocalFile($path) {
263
+        return $this->getCachedFile($path);
264
+    }
265
+
266
+    /**
267
+     * @param string $path
268
+     * @param string $target
269
+     */
270
+    private function addLocalFolder($path, $target) {
271
+        $dh = $this->opendir($path);
272
+        if (is_resource($dh)) {
273
+            while (($file = readdir($dh)) !== false) {
274
+                if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
275
+                    if ($this->is_dir($path . '/' . $file)) {
276
+                        mkdir($target . '/' . $file);
277
+                        $this->addLocalFolder($path . '/' . $file, $target . '/' . $file);
278
+                    } else {
279
+                        $tmp = $this->toTmpFile($path . '/' . $file);
280
+                        rename($tmp, $target . '/' . $file);
281
+                    }
282
+                }
283
+            }
284
+        }
285
+    }
286
+
287
+    /**
288
+     * @param string $query
289
+     * @param string $dir
290
+     * @return array
291
+     */
292
+    protected function searchInDir($query, $dir = '') {
293
+        $files = array();
294
+        $dh = $this->opendir($dir);
295
+        if (is_resource($dh)) {
296
+            while (($item = readdir($dh)) !== false) {
297
+                if (\OC\Files\Filesystem::isIgnoredDir($item)) continue;
298
+                if (strstr(strtolower($item), strtolower($query)) !== false) {
299
+                    $files[] = $dir . '/' . $item;
300
+                }
301
+                if ($this->is_dir($dir . '/' . $item)) {
302
+                    $files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
303
+                }
304
+            }
305
+        }
306
+        closedir($dh);
307
+        return $files;
308
+    }
309
+
310
+    /**
311
+     * check if a file or folder has been updated since $time
312
+     *
313
+     * The method is only used to check if the cache needs to be updated. Storage backends that don't support checking
314
+     * the mtime should always return false here. As a result storage implementations that always return false expect
315
+     * exclusive access to the backend and will not pick up files that have been added in a way that circumvents
316
+     * ownClouds filesystem.
317
+     *
318
+     * @param string $path
319
+     * @param int $time
320
+     * @return bool
321
+     */
322
+    public function hasUpdated($path, $time) {
323
+        return $this->filemtime($path) > $time;
324
+    }
325
+
326
+    public function getCache($path = '', $storage = null) {
327
+        if (!$storage) {
328
+            $storage = $this;
329
+        }
330
+        if (!isset($storage->cache)) {
331
+            $storage->cache = new Cache($storage);
332
+        }
333
+        return $storage->cache;
334
+    }
335
+
336
+    public function getScanner($path = '', $storage = null) {
337
+        if (!$storage) {
338
+            $storage = $this;
339
+        }
340
+        if (!isset($storage->scanner)) {
341
+            $storage->scanner = new Scanner($storage);
342
+        }
343
+        return $storage->scanner;
344
+    }
345
+
346
+    public function getWatcher($path = '', $storage = null) {
347
+        if (!$storage) {
348
+            $storage = $this;
349
+        }
350
+        if (!isset($this->watcher)) {
351
+            $this->watcher = new Watcher($storage);
352
+            $globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER);
353
+            $this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy));
354
+        }
355
+        return $this->watcher;
356
+    }
357
+
358
+    /**
359
+     * get a propagator instance for the cache
360
+     *
361
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
362
+     * @return \OC\Files\Cache\Propagator
363
+     */
364
+    public function getPropagator($storage = null) {
365
+        if (!$storage) {
366
+            $storage = $this;
367
+        }
368
+        if (!isset($storage->propagator)) {
369
+            $storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection());
370
+        }
371
+        return $storage->propagator;
372
+    }
373
+
374
+    public function getUpdater($storage = null) {
375
+        if (!$storage) {
376
+            $storage = $this;
377
+        }
378
+        if (!isset($storage->updater)) {
379
+            $storage->updater = new Updater($storage);
380
+        }
381
+        return $storage->updater;
382
+    }
383
+
384
+    public function getStorageCache($storage = null) {
385
+        if (!$storage) {
386
+            $storage = $this;
387
+        }
388
+        if (!isset($this->storageCache)) {
389
+            $this->storageCache = new \OC\Files\Cache\Storage($storage);
390
+        }
391
+        return $this->storageCache;
392
+    }
393
+
394
+    /**
395
+     * get the owner of a path
396
+     *
397
+     * @param string $path The path to get the owner
398
+     * @return string|false uid or false
399
+     */
400
+    public function getOwner($path) {
401
+        if ($this->owner === null) {
402
+            $this->owner = \OC_User::getUser();
403
+        }
404
+
405
+        return $this->owner;
406
+    }
407
+
408
+    /**
409
+     * get the ETag for a file or folder
410
+     *
411
+     * @param string $path
412
+     * @return string
413
+     */
414
+    public function getETag($path) {
415
+        return uniqid();
416
+    }
417
+
418
+    /**
419
+     * clean a path, i.e. remove all redundant '.' and '..'
420
+     * making sure that it can't point to higher than '/'
421
+     *
422
+     * @param string $path The path to clean
423
+     * @return string cleaned path
424
+     */
425
+    public function cleanPath($path) {
426
+        if (strlen($path) == 0 or $path[0] != '/') {
427
+            $path = '/' . $path;
428
+        }
429
+
430
+        $output = array();
431
+        foreach (explode('/', $path) as $chunk) {
432
+            if ($chunk == '..') {
433
+                array_pop($output);
434
+            } else if ($chunk == '.') {
435
+            } else {
436
+                $output[] = $chunk;
437
+            }
438
+        }
439
+        return implode('/', $output);
440
+    }
441
+
442
+    /**
443
+     * Test a storage for availability
444
+     *
445
+     * @return bool
446
+     */
447
+    public function test() {
448
+        try {
449
+            if ($this->stat('')) {
450
+                return true;
451
+            }
452
+            \OC::$server->getLogger()->info("External storage not available: stat() failed");
453
+            return false;
454
+        } catch (\Exception $e) {
455
+            \OC::$server->getLogger()->info("External storage not available: " . $e->getMessage());
456
+            \OC::$server->getLogger()->logException($e, ['level' => \OCP\Util::DEBUG]);
457
+            return false;
458
+        }
459
+    }
460
+
461
+    /**
462
+     * get the free space in the storage
463
+     *
464
+     * @param string $path
465
+     * @return int|false
466
+     */
467
+    public function free_space($path) {
468
+        return \OCP\Files\FileInfo::SPACE_UNKNOWN;
469
+    }
470
+
471
+    /**
472
+     * {@inheritdoc}
473
+     */
474
+    public function isLocal() {
475
+        // the common implementation returns a temporary file by
476
+        // default, which is not local
477
+        return false;
478
+    }
479
+
480
+    /**
481
+     * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
482
+     *
483
+     * @param string $class
484
+     * @return bool
485
+     */
486
+    public function instanceOfStorage($class) {
487
+        if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
488
+            // FIXME Temporary fix to keep existing checks working
489
+            $class = '\OCA\Files_Sharing\SharedStorage';
490
+        }
491
+        return is_a($this, $class);
492
+    }
493
+
494
+    /**
495
+     * A custom storage implementation can return an url for direct download of a give file.
496
+     *
497
+     * For now the returned array can hold the parameter url - in future more attributes might follow.
498
+     *
499
+     * @param string $path
500
+     * @return array|false
501
+     */
502
+    public function getDirectDownload($path) {
503
+        return [];
504
+    }
505
+
506
+    /**
507
+     * @inheritdoc
508
+     * @throws InvalidPathException
509
+     */
510
+    public function verifyPath($path, $fileName) {
511
+
512
+        // verify empty and dot files
513
+        $trimmed = trim($fileName);
514
+        if ($trimmed === '') {
515
+            throw new EmptyFileNameException();
516
+        }
517
+
518
+        if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
519
+            throw new InvalidDirectoryException();
520
+        }
521
+
522
+        if (!\OC::$server->getDatabaseConnection()->supports4ByteText()) {
523
+            // verify database - e.g. mysql only 3-byte chars
524
+            if (preg_match('%(?:
525 525
       \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
526 526
     | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
527 527
     | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
528 528
 )%xs', $fileName)) {
529
-				throw new InvalidCharacterInPathException();
530
-			}
531
-		}
532
-
533
-		if (isset($fileName[255])) {
534
-			throw new FileNameTooLongException();
535
-		}
536
-
537
-		// NOTE: $path will remain unverified for now
538
-		$this->verifyPosixPath($fileName);
539
-	}
540
-
541
-	/**
542
-	 * @param string $fileName
543
-	 * @throws InvalidPathException
544
-	 */
545
-	protected function verifyPosixPath($fileName) {
546
-		$fileName = trim($fileName);
547
-		$this->scanForInvalidCharacters($fileName, "\\/");
548
-		$reservedNames = ['*'];
549
-		if (in_array($fileName, $reservedNames)) {
550
-			throw new ReservedWordException();
551
-		}
552
-	}
553
-
554
-	/**
555
-	 * @param string $fileName
556
-	 * @param string $invalidChars
557
-	 * @throws InvalidPathException
558
-	 */
559
-	private function scanForInvalidCharacters($fileName, $invalidChars) {
560
-		foreach (str_split($invalidChars) as $char) {
561
-			if (strpos($fileName, $char) !== false) {
562
-				throw new InvalidCharacterInPathException();
563
-			}
564
-		}
565
-
566
-		$sanitizedFileName = filter_var($fileName, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
567
-		if ($sanitizedFileName !== $fileName) {
568
-			throw new InvalidCharacterInPathException();
569
-		}
570
-	}
571
-
572
-	/**
573
-	 * @param array $options
574
-	 */
575
-	public function setMountOptions(array $options) {
576
-		$this->mountOptions = $options;
577
-	}
578
-
579
-	/**
580
-	 * @param string $name
581
-	 * @param mixed $default
582
-	 * @return mixed
583
-	 */
584
-	public function getMountOption($name, $default = null) {
585
-		return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default;
586
-	}
587
-
588
-	/**
589
-	 * @param IStorage $sourceStorage
590
-	 * @param string $sourceInternalPath
591
-	 * @param string $targetInternalPath
592
-	 * @param bool $preserveMtime
593
-	 * @return bool
594
-	 */
595
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
596
-		if ($sourceStorage === $this) {
597
-			return $this->copy($sourceInternalPath, $targetInternalPath);
598
-		}
599
-
600
-		if ($sourceStorage->is_dir($sourceInternalPath)) {
601
-			$dh = $sourceStorage->opendir($sourceInternalPath);
602
-			$result = $this->mkdir($targetInternalPath);
603
-			if (is_resource($dh)) {
604
-				while ($result and ($file = readdir($dh)) !== false) {
605
-					if (!Filesystem::isIgnoredDir($file)) {
606
-						$result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file);
607
-					}
608
-				}
609
-			}
610
-		} else {
611
-			$source = $sourceStorage->fopen($sourceInternalPath, 'r');
612
-			// TODO: call fopen in a way that we execute again all storage wrappers
613
-			// to avoid that we bypass storage wrappers which perform important actions
614
-			// for this operation. Same is true for all other operations which
615
-			// are not the same as the original one.Once this is fixed we also
616
-			// need to adjust the encryption wrapper.
617
-			$target = $this->fopen($targetInternalPath, 'w');
618
-			list(, $result) = \OC_Helper::streamCopy($source, $target);
619
-			if ($result and $preserveMtime) {
620
-				$this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath));
621
-			}
622
-			fclose($source);
623
-			fclose($target);
624
-
625
-			if (!$result) {
626
-				// delete partially written target file
627
-				$this->unlink($targetInternalPath);
628
-				// delete cache entry that was created by fopen
629
-				$this->getCache()->remove($targetInternalPath);
630
-			}
631
-		}
632
-		return (bool)$result;
633
-	}
634
-
635
-	/**
636
-	 * @param IStorage $sourceStorage
637
-	 * @param string $sourceInternalPath
638
-	 * @param string $targetInternalPath
639
-	 * @return bool
640
-	 */
641
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
642
-		if ($sourceStorage === $this) {
643
-			return $this->rename($sourceInternalPath, $targetInternalPath);
644
-		}
645
-
646
-		if (!$sourceStorage->isDeletable($sourceInternalPath)) {
647
-			return false;
648
-		}
649
-
650
-		$result = $this->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, true);
651
-		if ($result) {
652
-			if ($sourceStorage->is_dir($sourceInternalPath)) {
653
-				$result &= $sourceStorage->rmdir($sourceInternalPath);
654
-			} else {
655
-				$result &= $sourceStorage->unlink($sourceInternalPath);
656
-			}
657
-		}
658
-		return $result;
659
-	}
660
-
661
-	/**
662
-	 * @inheritdoc
663
-	 */
664
-	public function getMetaData($path) {
665
-		$permissions = $this->getPermissions($path);
666
-		if (!$permissions & \OCP\Constants::PERMISSION_READ) {
667
-			//can't read, nothing we can do
668
-			return null;
669
-		}
670
-
671
-		$data = [];
672
-		$data['mimetype'] = $this->getMimeType($path);
673
-		$data['mtime'] = $this->filemtime($path);
674
-		if ($data['mtime'] === false) {
675
-			$data['mtime'] = time();
676
-		}
677
-		if ($data['mimetype'] == 'httpd/unix-directory') {
678
-			$data['size'] = -1; //unknown
679
-		} else {
680
-			$data['size'] = $this->filesize($path);
681
-		}
682
-		$data['etag'] = $this->getETag($path);
683
-		$data['storage_mtime'] = $data['mtime'];
684
-		$data['permissions'] = $permissions;
685
-
686
-		return $data;
687
-	}
688
-
689
-	/**
690
-	 * @param string $path
691
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
692
-	 * @param \OCP\Lock\ILockingProvider $provider
693
-	 * @throws \OCP\Lock\LockedException
694
-	 */
695
-	public function acquireLock($path, $type, ILockingProvider $provider) {
696
-		$logger = $this->getLockLogger();
697
-		if ($logger) {
698
-			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
699
-			$logger->info(
700
-				sprintf(
701
-					'acquire %s lock on "%s" on storage "%s"',
702
-					$typeString,
703
-					$path,
704
-					$this->getId()
705
-				),
706
-				[
707
-					'app' => 'locking',
708
-				]
709
-			);
710
-		}
711
-		try {
712
-			$provider->acquireLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
713
-		} catch (LockedException $e) {
714
-			if ($logger) {
715
-				$logger->logException($e);
716
-			}
717
-			throw $e;
718
-		}
719
-	}
720
-
721
-	/**
722
-	 * @param string $path
723
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
724
-	 * @param \OCP\Lock\ILockingProvider $provider
725
-	 * @throws \OCP\Lock\LockedException
726
-	 */
727
-	public function releaseLock($path, $type, ILockingProvider $provider) {
728
-		$logger = $this->getLockLogger();
729
-		if ($logger) {
730
-			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
731
-			$logger->info(
732
-				sprintf(
733
-					'release %s lock on "%s" on storage "%s"',
734
-					$typeString,
735
-					$path,
736
-					$this->getId()
737
-				),
738
-				[
739
-					'app' => 'locking',
740
-				]
741
-			);
742
-		}
743
-		try {
744
-			$provider->releaseLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
745
-		} catch (LockedException $e) {
746
-			if ($logger) {
747
-				$logger->logException($e);
748
-			}
749
-			throw $e;
750
-		}
751
-	}
752
-
753
-	/**
754
-	 * @param string $path
755
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
756
-	 * @param \OCP\Lock\ILockingProvider $provider
757
-	 * @throws \OCP\Lock\LockedException
758
-	 */
759
-	public function changeLock($path, $type, ILockingProvider $provider) {
760
-		$logger = $this->getLockLogger();
761
-		if ($logger) {
762
-			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
763
-			$logger->info(
764
-				sprintf(
765
-					'change lock on "%s" to %s on storage "%s"',
766
-					$path,
767
-					$typeString,
768
-					$this->getId()
769
-				),
770
-				[
771
-					'app' => 'locking',
772
-				]
773
-			);
774
-		}
775
-		try {
776
-			$provider->changeLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
777
-		} catch (LockedException $e) {
778
-			if ($logger) {
779
-				$logger->logException($e);
780
-			}
781
-			throw $e;
782
-		}
783
-	}
784
-
785
-	private function getLockLogger() {
786
-		if (is_null($this->shouldLogLocks)) {
787
-			$this->shouldLogLocks = \OC::$server->getConfig()->getSystemValue('filelocking.debug', false);
788
-			$this->logger = $this->shouldLogLocks ? \OC::$server->getLogger() : null;
789
-		}
790
-		return $this->logger;
791
-	}
792
-
793
-	/**
794
-	 * @return array [ available, last_checked ]
795
-	 */
796
-	public function getAvailability() {
797
-		return $this->getStorageCache()->getAvailability();
798
-	}
799
-
800
-	/**
801
-	 * @param bool $isAvailable
802
-	 */
803
-	public function setAvailability($isAvailable) {
804
-		$this->getStorageCache()->setAvailability($isAvailable);
805
-	}
806
-
807
-	/**
808
-	 * @return bool
809
-	 */
810
-	public function needsPartFile() {
811
-		return true;
812
-	}
529
+                throw new InvalidCharacterInPathException();
530
+            }
531
+        }
532
+
533
+        if (isset($fileName[255])) {
534
+            throw new FileNameTooLongException();
535
+        }
536
+
537
+        // NOTE: $path will remain unverified for now
538
+        $this->verifyPosixPath($fileName);
539
+    }
540
+
541
+    /**
542
+     * @param string $fileName
543
+     * @throws InvalidPathException
544
+     */
545
+    protected function verifyPosixPath($fileName) {
546
+        $fileName = trim($fileName);
547
+        $this->scanForInvalidCharacters($fileName, "\\/");
548
+        $reservedNames = ['*'];
549
+        if (in_array($fileName, $reservedNames)) {
550
+            throw new ReservedWordException();
551
+        }
552
+    }
553
+
554
+    /**
555
+     * @param string $fileName
556
+     * @param string $invalidChars
557
+     * @throws InvalidPathException
558
+     */
559
+    private function scanForInvalidCharacters($fileName, $invalidChars) {
560
+        foreach (str_split($invalidChars) as $char) {
561
+            if (strpos($fileName, $char) !== false) {
562
+                throw new InvalidCharacterInPathException();
563
+            }
564
+        }
565
+
566
+        $sanitizedFileName = filter_var($fileName, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
567
+        if ($sanitizedFileName !== $fileName) {
568
+            throw new InvalidCharacterInPathException();
569
+        }
570
+    }
571
+
572
+    /**
573
+     * @param array $options
574
+     */
575
+    public function setMountOptions(array $options) {
576
+        $this->mountOptions = $options;
577
+    }
578
+
579
+    /**
580
+     * @param string $name
581
+     * @param mixed $default
582
+     * @return mixed
583
+     */
584
+    public function getMountOption($name, $default = null) {
585
+        return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default;
586
+    }
587
+
588
+    /**
589
+     * @param IStorage $sourceStorage
590
+     * @param string $sourceInternalPath
591
+     * @param string $targetInternalPath
592
+     * @param bool $preserveMtime
593
+     * @return bool
594
+     */
595
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
596
+        if ($sourceStorage === $this) {
597
+            return $this->copy($sourceInternalPath, $targetInternalPath);
598
+        }
599
+
600
+        if ($sourceStorage->is_dir($sourceInternalPath)) {
601
+            $dh = $sourceStorage->opendir($sourceInternalPath);
602
+            $result = $this->mkdir($targetInternalPath);
603
+            if (is_resource($dh)) {
604
+                while ($result and ($file = readdir($dh)) !== false) {
605
+                    if (!Filesystem::isIgnoredDir($file)) {
606
+                        $result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file);
607
+                    }
608
+                }
609
+            }
610
+        } else {
611
+            $source = $sourceStorage->fopen($sourceInternalPath, 'r');
612
+            // TODO: call fopen in a way that we execute again all storage wrappers
613
+            // to avoid that we bypass storage wrappers which perform important actions
614
+            // for this operation. Same is true for all other operations which
615
+            // are not the same as the original one.Once this is fixed we also
616
+            // need to adjust the encryption wrapper.
617
+            $target = $this->fopen($targetInternalPath, 'w');
618
+            list(, $result) = \OC_Helper::streamCopy($source, $target);
619
+            if ($result and $preserveMtime) {
620
+                $this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath));
621
+            }
622
+            fclose($source);
623
+            fclose($target);
624
+
625
+            if (!$result) {
626
+                // delete partially written target file
627
+                $this->unlink($targetInternalPath);
628
+                // delete cache entry that was created by fopen
629
+                $this->getCache()->remove($targetInternalPath);
630
+            }
631
+        }
632
+        return (bool)$result;
633
+    }
634
+
635
+    /**
636
+     * @param IStorage $sourceStorage
637
+     * @param string $sourceInternalPath
638
+     * @param string $targetInternalPath
639
+     * @return bool
640
+     */
641
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
642
+        if ($sourceStorage === $this) {
643
+            return $this->rename($sourceInternalPath, $targetInternalPath);
644
+        }
645
+
646
+        if (!$sourceStorage->isDeletable($sourceInternalPath)) {
647
+            return false;
648
+        }
649
+
650
+        $result = $this->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, true);
651
+        if ($result) {
652
+            if ($sourceStorage->is_dir($sourceInternalPath)) {
653
+                $result &= $sourceStorage->rmdir($sourceInternalPath);
654
+            } else {
655
+                $result &= $sourceStorage->unlink($sourceInternalPath);
656
+            }
657
+        }
658
+        return $result;
659
+    }
660
+
661
+    /**
662
+     * @inheritdoc
663
+     */
664
+    public function getMetaData($path) {
665
+        $permissions = $this->getPermissions($path);
666
+        if (!$permissions & \OCP\Constants::PERMISSION_READ) {
667
+            //can't read, nothing we can do
668
+            return null;
669
+        }
670
+
671
+        $data = [];
672
+        $data['mimetype'] = $this->getMimeType($path);
673
+        $data['mtime'] = $this->filemtime($path);
674
+        if ($data['mtime'] === false) {
675
+            $data['mtime'] = time();
676
+        }
677
+        if ($data['mimetype'] == 'httpd/unix-directory') {
678
+            $data['size'] = -1; //unknown
679
+        } else {
680
+            $data['size'] = $this->filesize($path);
681
+        }
682
+        $data['etag'] = $this->getETag($path);
683
+        $data['storage_mtime'] = $data['mtime'];
684
+        $data['permissions'] = $permissions;
685
+
686
+        return $data;
687
+    }
688
+
689
+    /**
690
+     * @param string $path
691
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
692
+     * @param \OCP\Lock\ILockingProvider $provider
693
+     * @throws \OCP\Lock\LockedException
694
+     */
695
+    public function acquireLock($path, $type, ILockingProvider $provider) {
696
+        $logger = $this->getLockLogger();
697
+        if ($logger) {
698
+            $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
699
+            $logger->info(
700
+                sprintf(
701
+                    'acquire %s lock on "%s" on storage "%s"',
702
+                    $typeString,
703
+                    $path,
704
+                    $this->getId()
705
+                ),
706
+                [
707
+                    'app' => 'locking',
708
+                ]
709
+            );
710
+        }
711
+        try {
712
+            $provider->acquireLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
713
+        } catch (LockedException $e) {
714
+            if ($logger) {
715
+                $logger->logException($e);
716
+            }
717
+            throw $e;
718
+        }
719
+    }
720
+
721
+    /**
722
+     * @param string $path
723
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
724
+     * @param \OCP\Lock\ILockingProvider $provider
725
+     * @throws \OCP\Lock\LockedException
726
+     */
727
+    public function releaseLock($path, $type, ILockingProvider $provider) {
728
+        $logger = $this->getLockLogger();
729
+        if ($logger) {
730
+            $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
731
+            $logger->info(
732
+                sprintf(
733
+                    'release %s lock on "%s" on storage "%s"',
734
+                    $typeString,
735
+                    $path,
736
+                    $this->getId()
737
+                ),
738
+                [
739
+                    'app' => 'locking',
740
+                ]
741
+            );
742
+        }
743
+        try {
744
+            $provider->releaseLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
745
+        } catch (LockedException $e) {
746
+            if ($logger) {
747
+                $logger->logException($e);
748
+            }
749
+            throw $e;
750
+        }
751
+    }
752
+
753
+    /**
754
+     * @param string $path
755
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
756
+     * @param \OCP\Lock\ILockingProvider $provider
757
+     * @throws \OCP\Lock\LockedException
758
+     */
759
+    public function changeLock($path, $type, ILockingProvider $provider) {
760
+        $logger = $this->getLockLogger();
761
+        if ($logger) {
762
+            $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
763
+            $logger->info(
764
+                sprintf(
765
+                    'change lock on "%s" to %s on storage "%s"',
766
+                    $path,
767
+                    $typeString,
768
+                    $this->getId()
769
+                ),
770
+                [
771
+                    'app' => 'locking',
772
+                ]
773
+            );
774
+        }
775
+        try {
776
+            $provider->changeLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
777
+        } catch (LockedException $e) {
778
+            if ($logger) {
779
+                $logger->logException($e);
780
+            }
781
+            throw $e;
782
+        }
783
+    }
784
+
785
+    private function getLockLogger() {
786
+        if (is_null($this->shouldLogLocks)) {
787
+            $this->shouldLogLocks = \OC::$server->getConfig()->getSystemValue('filelocking.debug', false);
788
+            $this->logger = $this->shouldLogLocks ? \OC::$server->getLogger() : null;
789
+        }
790
+        return $this->logger;
791
+    }
792
+
793
+    /**
794
+     * @return array [ available, last_checked ]
795
+     */
796
+    public function getAvailability() {
797
+        return $this->getStorageCache()->getAvailability();
798
+    }
799
+
800
+    /**
801
+     * @param bool $isAvailable
802
+     */
803
+    public function setAvailability($isAvailable) {
804
+        $this->getStorageCache()->setAvailability($isAvailable);
805
+    }
806
+
807
+    /**
808
+     * @return bool
809
+     */
810
+    public function needsPartFile() {
811
+        return true;
812
+    }
813 813
 }
Please login to merge, or discard this patch.
Spacing   +17 added lines, -17 removed lines patch added patch discarded remove patch
@@ -218,7 +218,7 @@  discard block
 block discarded – undo
218 218
 			$this->mkdir($path2);
219 219
 			while ($file = readdir($dir)) {
220 220
 				if (!Filesystem::isIgnoredDir($file)) {
221
-					if (!$this->copy($path1 . '/' . $file, $path2 . '/' . $file)) {
221
+					if (!$this->copy($path1.'/'.$file, $path2.'/'.$file)) {
222 222
 						return false;
223 223
 					}
224 224
 				}
@@ -272,12 +272,12 @@  discard block
 block discarded – undo
272 272
 		if (is_resource($dh)) {
273 273
 			while (($file = readdir($dh)) !== false) {
274 274
 				if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
275
-					if ($this->is_dir($path . '/' . $file)) {
276
-						mkdir($target . '/' . $file);
277
-						$this->addLocalFolder($path . '/' . $file, $target . '/' . $file);
275
+					if ($this->is_dir($path.'/'.$file)) {
276
+						mkdir($target.'/'.$file);
277
+						$this->addLocalFolder($path.'/'.$file, $target.'/'.$file);
278 278
 					} else {
279
-						$tmp = $this->toTmpFile($path . '/' . $file);
280
-						rename($tmp, $target . '/' . $file);
279
+						$tmp = $this->toTmpFile($path.'/'.$file);
280
+						rename($tmp, $target.'/'.$file);
281 281
 					}
282 282
 				}
283 283
 			}
@@ -296,10 +296,10 @@  discard block
 block discarded – undo
296 296
 			while (($item = readdir($dh)) !== false) {
297 297
 				if (\OC\Files\Filesystem::isIgnoredDir($item)) continue;
298 298
 				if (strstr(strtolower($item), strtolower($query)) !== false) {
299
-					$files[] = $dir . '/' . $item;
299
+					$files[] = $dir.'/'.$item;
300 300
 				}
301
-				if ($this->is_dir($dir . '/' . $item)) {
302
-					$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
301
+				if ($this->is_dir($dir.'/'.$item)) {
302
+					$files = array_merge($files, $this->searchInDir($query, $dir.'/'.$item));
303 303
 				}
304 304
 			}
305 305
 		}
@@ -350,7 +350,7 @@  discard block
 block discarded – undo
350 350
 		if (!isset($this->watcher)) {
351 351
 			$this->watcher = new Watcher($storage);
352 352
 			$globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER);
353
-			$this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy));
353
+			$this->watcher->setPolicy((int) $this->getMountOption('filesystem_check_changes', $globalPolicy));
354 354
 		}
355 355
 		return $this->watcher;
356 356
 	}
@@ -424,7 +424,7 @@  discard block
 block discarded – undo
424 424
 	 */
425 425
 	public function cleanPath($path) {
426 426
 		if (strlen($path) == 0 or $path[0] != '/') {
427
-			$path = '/' . $path;
427
+			$path = '/'.$path;
428 428
 		}
429 429
 
430 430
 		$output = array();
@@ -452,7 +452,7 @@  discard block
 block discarded – undo
452 452
 			\OC::$server->getLogger()->info("External storage not available: stat() failed");
453 453
 			return false;
454 454
 		} catch (\Exception $e) {
455
-			\OC::$server->getLogger()->info("External storage not available: " . $e->getMessage());
455
+			\OC::$server->getLogger()->info("External storage not available: ".$e->getMessage());
456 456
 			\OC::$server->getLogger()->logException($e, ['level' => \OCP\Util::DEBUG]);
457 457
 			return false;
458 458
 		}
@@ -603,7 +603,7 @@  discard block
 block discarded – undo
603 603
 			if (is_resource($dh)) {
604 604
 				while ($result and ($file = readdir($dh)) !== false) {
605 605
 					if (!Filesystem::isIgnoredDir($file)) {
606
-						$result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file);
606
+						$result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath.'/'.$file, $targetInternalPath.'/'.$file);
607 607
 					}
608 608
 				}
609 609
 			}
@@ -629,7 +629,7 @@  discard block
 block discarded – undo
629 629
 				$this->getCache()->remove($targetInternalPath);
630 630
 			}
631 631
 		}
632
-		return (bool)$result;
632
+		return (bool) $result;
633 633
 	}
634 634
 
635 635
 	/**
@@ -709,7 +709,7 @@  discard block
 block discarded – undo
709 709
 			);
710 710
 		}
711 711
 		try {
712
-			$provider->acquireLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
712
+			$provider->acquireLock('files/'.md5($this->getId().'::'.trim($path, '/')), $type);
713 713
 		} catch (LockedException $e) {
714 714
 			if ($logger) {
715 715
 				$logger->logException($e);
@@ -741,7 +741,7 @@  discard block
 block discarded – undo
741 741
 			);
742 742
 		}
743 743
 		try {
744
-			$provider->releaseLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
744
+			$provider->releaseLock('files/'.md5($this->getId().'::'.trim($path, '/')), $type);
745 745
 		} catch (LockedException $e) {
746 746
 			if ($logger) {
747 747
 				$logger->logException($e);
@@ -773,7 +773,7 @@  discard block
 block discarded – undo
773 773
 			);
774 774
 		}
775 775
 		try {
776
-			$provider->changeLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
776
+			$provider->changeLock('files/'.md5($this->getId().'::'.trim($path, '/')), $type);
777 777
 		} catch (LockedException $e) {
778 778
 			if ($logger) {
779 779
 				$logger->logException($e);
Please login to merge, or discard this patch.
lib/private/Files/Storage/StorageFactory.php 2 patches
Indentation   +70 added lines, -70 removed lines patch added patch discarded remove patch
@@ -29,80 +29,80 @@
 block discarded – undo
29 29
 use OCP\Files\Storage\IStorageFactory;
30 30
 
31 31
 class StorageFactory implements IStorageFactory {
32
-	/**
33
-	 * @var array[] [$name=>['priority'=>$priority, 'wrapper'=>$callable] $storageWrappers
34
-	 */
35
-	private $storageWrappers = [];
32
+    /**
33
+     * @var array[] [$name=>['priority'=>$priority, 'wrapper'=>$callable] $storageWrappers
34
+     */
35
+    private $storageWrappers = [];
36 36
 
37
-	/**
38
-	 * allow modifier storage behaviour by adding wrappers around storages
39
-	 *
40
-	 * $callback should be a function of type (string $mountPoint, Storage $storage) => Storage
41
-	 *
42
-	 * @param string $wrapperName name of the wrapper
43
-	 * @param callable $callback callback
44
-	 * @param int $priority wrappers with the lower priority are applied last (meaning they get called first)
45
-	 * @param \OCP\Files\Mount\IMountPoint[] $existingMounts existing mount points to apply the wrapper to
46
-	 * @return bool true if the wrapper was added, false if there was already a wrapper with this
47
-	 * name registered
48
-	 */
49
-	public function addStorageWrapper($wrapperName, $callback, $priority = 50, $existingMounts = []) {
50
-		if (isset($this->storageWrappers[$wrapperName])) {
51
-			return false;
52
-		}
37
+    /**
38
+     * allow modifier storage behaviour by adding wrappers around storages
39
+     *
40
+     * $callback should be a function of type (string $mountPoint, Storage $storage) => Storage
41
+     *
42
+     * @param string $wrapperName name of the wrapper
43
+     * @param callable $callback callback
44
+     * @param int $priority wrappers with the lower priority are applied last (meaning they get called first)
45
+     * @param \OCP\Files\Mount\IMountPoint[] $existingMounts existing mount points to apply the wrapper to
46
+     * @return bool true if the wrapper was added, false if there was already a wrapper with this
47
+     * name registered
48
+     */
49
+    public function addStorageWrapper($wrapperName, $callback, $priority = 50, $existingMounts = []) {
50
+        if (isset($this->storageWrappers[$wrapperName])) {
51
+            return false;
52
+        }
53 53
 
54
-		// apply to existing mounts before registering it to prevent applying it double in MountPoint::createStorage
55
-		foreach ($existingMounts as $mount) {
56
-			$mount->wrapStorage($callback);
57
-		}
54
+        // apply to existing mounts before registering it to prevent applying it double in MountPoint::createStorage
55
+        foreach ($existingMounts as $mount) {
56
+            $mount->wrapStorage($callback);
57
+        }
58 58
 
59
-		$this->storageWrappers[$wrapperName] = ['wrapper' => $callback, 'priority' => $priority];
60
-		return true;
61
-	}
59
+        $this->storageWrappers[$wrapperName] = ['wrapper' => $callback, 'priority' => $priority];
60
+        return true;
61
+    }
62 62
 
63
-	/**
64
-	 * Remove a storage wrapper by name.
65
-	 * Note: internal method only to be used for cleanup
66
-	 *
67
-	 * @param string $wrapperName name of the wrapper
68
-	 * @internal
69
-	 */
70
-	public function removeStorageWrapper($wrapperName) {
71
-		unset($this->storageWrappers[$wrapperName]);
72
-	}
63
+    /**
64
+     * Remove a storage wrapper by name.
65
+     * Note: internal method only to be used for cleanup
66
+     *
67
+     * @param string $wrapperName name of the wrapper
68
+     * @internal
69
+     */
70
+    public function removeStorageWrapper($wrapperName) {
71
+        unset($this->storageWrappers[$wrapperName]);
72
+    }
73 73
 
74
-	/**
75
-	 * Create an instance of a storage and apply the registered storage wrappers
76
-	 *
77
-	 * @param \OCP\Files\Mount\IMountPoint $mountPoint
78
-	 * @param string $class
79
-	 * @param array $arguments
80
-	 * @return \OCP\Files\Storage
81
-	 */
82
-	public function getInstance(IMountPoint $mountPoint, $class, $arguments) {
83
-		return $this->wrap($mountPoint, new $class($arguments));
84
-	}
74
+    /**
75
+     * Create an instance of a storage and apply the registered storage wrappers
76
+     *
77
+     * @param \OCP\Files\Mount\IMountPoint $mountPoint
78
+     * @param string $class
79
+     * @param array $arguments
80
+     * @return \OCP\Files\Storage
81
+     */
82
+    public function getInstance(IMountPoint $mountPoint, $class, $arguments) {
83
+        return $this->wrap($mountPoint, new $class($arguments));
84
+    }
85 85
 
86
-	/**
87
-	 * @param \OCP\Files\Mount\IMountPoint $mountPoint
88
-	 * @param \OCP\Files\Storage $storage
89
-	 * @return \OCP\Files\Storage
90
-	 */
91
-	public function wrap(IMountPoint $mountPoint, $storage) {
92
-		$wrappers = array_values($this->storageWrappers);
93
-		usort($wrappers, function ($a, $b) {
94
-			return $b['priority'] - $a['priority'];
95
-		});
96
-		/** @var callable[] $wrappers */
97
-		$wrappers = array_map(function ($wrapper) {
98
-			return $wrapper['wrapper'];
99
-		}, $wrappers);
100
-		foreach ($wrappers as $wrapper) {
101
-			$storage = $wrapper($mountPoint->getMountPoint(), $storage, $mountPoint);
102
-			if (!($storage instanceof \OCP\Files\Storage)) {
103
-				throw new \Exception('Invalid result from storage wrapper');
104
-			}
105
-		}
106
-		return $storage;
107
-	}
86
+    /**
87
+     * @param \OCP\Files\Mount\IMountPoint $mountPoint
88
+     * @param \OCP\Files\Storage $storage
89
+     * @return \OCP\Files\Storage
90
+     */
91
+    public function wrap(IMountPoint $mountPoint, $storage) {
92
+        $wrappers = array_values($this->storageWrappers);
93
+        usort($wrappers, function ($a, $b) {
94
+            return $b['priority'] - $a['priority'];
95
+        });
96
+        /** @var callable[] $wrappers */
97
+        $wrappers = array_map(function ($wrapper) {
98
+            return $wrapper['wrapper'];
99
+        }, $wrappers);
100
+        foreach ($wrappers as $wrapper) {
101
+            $storage = $wrapper($mountPoint->getMountPoint(), $storage, $mountPoint);
102
+            if (!($storage instanceof \OCP\Files\Storage)) {
103
+                throw new \Exception('Invalid result from storage wrapper');
104
+            }
105
+        }
106
+        return $storage;
107
+    }
108 108
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -90,11 +90,11 @@
 block discarded – undo
90 90
 	 */
91 91
 	public function wrap(IMountPoint $mountPoint, $storage) {
92 92
 		$wrappers = array_values($this->storageWrappers);
93
-		usort($wrappers, function ($a, $b) {
93
+		usort($wrappers, function($a, $b) {
94 94
 			return $b['priority'] - $a['priority'];
95 95
 		});
96 96
 		/** @var callable[] $wrappers */
97
-		$wrappers = array_map(function ($wrapper) {
97
+		$wrappers = array_map(function($wrapper) {
98 98
 			return $wrapper['wrapper'];
99 99
 		}, $wrappers);
100 100
 		foreach ($wrappers as $wrapper) {
Please login to merge, or discard this patch.