Passed
Push — master ( 62403d...0c3e2f )
by Joas
14:50 queued 14s
created
lib/private/Files/Storage/Local.php 1 patch
Indentation   +452 added lines, -452 removed lines patch added patch discarded remove patch
@@ -50,456 +50,456 @@
 block discarded – undo
50 50
  * for local filestore, we only have to map the paths
51 51
  */
52 52
 class Local extends \OC\Files\Storage\Common {
53
-	protected $datadir;
54
-
55
-	protected $dataDirLength;
56
-
57
-	protected $allowSymlinks = false;
58
-
59
-	protected $realDataDir;
60
-
61
-	public function __construct($arguments) {
62
-		if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) {
63
-			throw new \InvalidArgumentException('No data directory set for local storage');
64
-		}
65
-		$this->datadir = str_replace('//', '/', $arguments['datadir']);
66
-		// some crazy code uses a local storage on root...
67
-		if ($this->datadir === '/') {
68
-			$this->realDataDir = $this->datadir;
69
-		} else {
70
-			$realPath = realpath($this->datadir) ?: $this->datadir;
71
-			$this->realDataDir = rtrim($realPath, '/') . '/';
72
-		}
73
-		if (substr($this->datadir, -1) !== '/') {
74
-			$this->datadir .= '/';
75
-		}
76
-		$this->dataDirLength = strlen($this->realDataDir);
77
-	}
78
-
79
-	public function __destruct() {
80
-	}
81
-
82
-	public function getId() {
83
-		return 'local::' . $this->datadir;
84
-	}
85
-
86
-	public function mkdir($path) {
87
-		return @mkdir($this->getSourcePath($path), 0777, true);
88
-	}
89
-
90
-	public function rmdir($path) {
91
-		if (!$this->isDeletable($path)) {
92
-			return false;
93
-		}
94
-		try {
95
-			$it = new \RecursiveIteratorIterator(
96
-				new \RecursiveDirectoryIterator($this->getSourcePath($path)),
97
-				\RecursiveIteratorIterator::CHILD_FIRST
98
-			);
99
-			/**
100
-			 * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
101
-			 * This bug is fixed in PHP 5.5.9 or before
102
-			 * See #8376
103
-			 */
104
-			$it->rewind();
105
-			while ($it->valid()) {
106
-				/**
107
-				 * @var \SplFileInfo $file
108
-				 */
109
-				$file = $it->current();
110
-				if (in_array($file->getBasename(), ['.', '..'])) {
111
-					$it->next();
112
-					continue;
113
-				} elseif ($file->isDir()) {
114
-					rmdir($file->getPathname());
115
-				} elseif ($file->isFile() || $file->isLink()) {
116
-					unlink($file->getPathname());
117
-				}
118
-				$it->next();
119
-			}
120
-			return rmdir($this->getSourcePath($path));
121
-		} catch (\UnexpectedValueException $e) {
122
-			return false;
123
-		}
124
-	}
125
-
126
-	public function opendir($path) {
127
-		return opendir($this->getSourcePath($path));
128
-	}
129
-
130
-	public function is_dir($path) {
131
-		if (substr($path, -1) == '/') {
132
-			$path = substr($path, 0, -1);
133
-		}
134
-		return is_dir($this->getSourcePath($path));
135
-	}
136
-
137
-	public function is_file($path) {
138
-		return is_file($this->getSourcePath($path));
139
-	}
140
-
141
-	public function stat($path) {
142
-		clearstatcache();
143
-		$fullPath = $this->getSourcePath($path);
144
-		$statResult = stat($fullPath);
145
-		if (PHP_INT_SIZE === 4 && !$this->is_dir($path)) {
146
-			$filesize = $this->filesize($path);
147
-			$statResult['size'] = $filesize;
148
-			$statResult[7] = $filesize;
149
-		}
150
-		return $statResult;
151
-	}
152
-
153
-	public function filetype($path) {
154
-		$filetype = filetype($this->getSourcePath($path));
155
-		if ($filetype == 'link') {
156
-			$filetype = filetype(realpath($this->getSourcePath($path)));
157
-		}
158
-		return $filetype;
159
-	}
160
-
161
-	public function filesize($path) {
162
-		if ($this->is_dir($path)) {
163
-			return 0;
164
-		}
165
-		$fullPath = $this->getSourcePath($path);
166
-		if (PHP_INT_SIZE === 4) {
167
-			$helper = new \OC\LargeFileHelper;
168
-			return $helper->getFileSize($fullPath);
169
-		}
170
-		return filesize($fullPath);
171
-	}
172
-
173
-	public function isReadable($path) {
174
-		return is_readable($this->getSourcePath($path));
175
-	}
176
-
177
-	public function isUpdatable($path) {
178
-		return is_writable($this->getSourcePath($path));
179
-	}
180
-
181
-	public function file_exists($path) {
182
-		return file_exists($this->getSourcePath($path));
183
-	}
184
-
185
-	public function filemtime($path) {
186
-		$fullPath = $this->getSourcePath($path);
187
-		clearstatcache(true, $fullPath);
188
-		if (!$this->file_exists($path)) {
189
-			return false;
190
-		}
191
-		if (PHP_INT_SIZE === 4) {
192
-			$helper = new \OC\LargeFileHelper();
193
-			return $helper->getFileMtime($fullPath);
194
-		}
195
-		return filemtime($fullPath);
196
-	}
197
-
198
-	public function touch($path, $mtime = null) {
199
-		// sets the modification time of the file to the given value.
200
-		// If mtime is nil the current time is set.
201
-		// note that the access time of the file always changes to the current time.
202
-		if ($this->file_exists($path) and !$this->isUpdatable($path)) {
203
-			return false;
204
-		}
205
-		if (!is_null($mtime)) {
206
-			$result = @touch($this->getSourcePath($path), $mtime);
207
-		} else {
208
-			$result = @touch($this->getSourcePath($path));
209
-		}
210
-		if ($result) {
211
-			clearstatcache(true, $this->getSourcePath($path));
212
-		}
213
-
214
-		return $result;
215
-	}
216
-
217
-	public function file_get_contents($path) {
218
-		return file_get_contents($this->getSourcePath($path));
219
-	}
220
-
221
-	public function file_put_contents($path, $data) {
222
-		return file_put_contents($this->getSourcePath($path), $data);
223
-	}
224
-
225
-	public function unlink($path) {
226
-		if ($this->is_dir($path)) {
227
-			return $this->rmdir($path);
228
-		} else if ($this->is_file($path)) {
229
-			return unlink($this->getSourcePath($path));
230
-		} else {
231
-			return false;
232
-		}
233
-
234
-	}
235
-
236
-	private function treeContainsBlacklistedFile(string $path): bool {
237
-		$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path));
238
-		foreach ($iterator as $file) {
239
-			/** @var \SplFileInfo $file */
240
-			if (Filesystem::isFileBlacklisted($file->getBasename())) {
241
-				return true;
242
-			}
243
-		}
244
-
245
-		return false;
246
-	}
247
-
248
-	public function rename($path1, $path2) {
249
-		$srcParent = dirname($path1);
250
-		$dstParent = dirname($path2);
251
-
252
-		if (!$this->isUpdatable($srcParent)) {
253
-			\OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : ' . $srcParent, ILogger::ERROR);
254
-			return false;
255
-		}
256
-
257
-		if (!$this->isUpdatable($dstParent)) {
258
-			\OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : ' . $dstParent, ILogger::ERROR);
259
-			return false;
260
-		}
261
-
262
-		if (!$this->file_exists($path1)) {
263
-			\OCP\Util::writeLog('core', 'unable to rename, file does not exists : ' . $path1, ILogger::ERROR);
264
-			return false;
265
-		}
266
-
267
-		if ($this->is_dir($path2)) {
268
-			$this->rmdir($path2);
269
-		} else if ($this->is_file($path2)) {
270
-			$this->unlink($path2);
271
-		}
272
-
273
-		if ($this->is_dir($path1)) {
274
-			// we can't move folders across devices, use copy instead
275
-			$stat1 = stat(dirname($this->getSourcePath($path1)));
276
-			$stat2 = stat(dirname($this->getSourcePath($path2)));
277
-			if ($stat1['dev'] !== $stat2['dev']) {
278
-				$result = $this->copy($path1, $path2);
279
-				if ($result) {
280
-					$result &= $this->rmdir($path1);
281
-				}
282
-				return $result;
283
-			}
284
-
285
-			if ($this->treeContainsBlacklistedFile($this->getSourcePath($path1))) {
286
-				throw new ForbiddenException('Invalid path', false);
287
-			}
288
-		}
289
-
290
-		return rename($this->getSourcePath($path1), $this->getSourcePath($path2));
291
-	}
292
-
293
-	public function copy($path1, $path2) {
294
-		if ($this->is_dir($path1)) {
295
-			return parent::copy($path1, $path2);
296
-		} else {
297
-			return copy($this->getSourcePath($path1), $this->getSourcePath($path2));
298
-		}
299
-	}
300
-
301
-	public function fopen($path, $mode) {
302
-		return fopen($this->getSourcePath($path), $mode);
303
-	}
304
-
305
-	public function hash($type, $path, $raw = false) {
306
-		return hash_file($type, $this->getSourcePath($path), $raw);
307
-	}
308
-
309
-	public function free_space($path) {
310
-		$sourcePath = $this->getSourcePath($path);
311
-		// using !is_dir because $sourcePath might be a part file or
312
-		// non-existing file, so we'd still want to use the parent dir
313
-		// in such cases
314
-		if (!is_dir($sourcePath)) {
315
-			// disk_free_space doesn't work on files
316
-			$sourcePath = dirname($sourcePath);
317
-		}
318
-		$space = @disk_free_space($sourcePath);
319
-		if ($space === false || is_null($space)) {
320
-			return \OCP\Files\FileInfo::SPACE_UNKNOWN;
321
-		}
322
-		return $space;
323
-	}
324
-
325
-	public function search($query) {
326
-		return $this->searchInDir($query);
327
-	}
328
-
329
-	public function getLocalFile($path) {
330
-		return $this->getSourcePath($path);
331
-	}
332
-
333
-	public function getLocalFolder($path) {
334
-		return $this->getSourcePath($path);
335
-	}
336
-
337
-	/**
338
-	 * @param string $query
339
-	 * @param string $dir
340
-	 * @return array
341
-	 */
342
-	protected function searchInDir($query, $dir = '') {
343
-		$files = [];
344
-		$physicalDir = $this->getSourcePath($dir);
345
-		foreach (scandir($physicalDir) as $item) {
346
-			if (\OC\Files\Filesystem::isIgnoredDir($item))
347
-				continue;
348
-			$physicalItem = $physicalDir . '/' . $item;
349
-
350
-			if (strstr(strtolower($item), strtolower($query)) !== false) {
351
-				$files[] = $dir . '/' . $item;
352
-			}
353
-			if (is_dir($physicalItem)) {
354
-				$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
355
-			}
356
-		}
357
-		return $files;
358
-	}
359
-
360
-	/**
361
-	 * check if a file or folder has been updated since $time
362
-	 *
363
-	 * @param string $path
364
-	 * @param int $time
365
-	 * @return bool
366
-	 */
367
-	public function hasUpdated($path, $time) {
368
-		if ($this->file_exists($path)) {
369
-			return $this->filemtime($path) > $time;
370
-		} else {
371
-			return true;
372
-		}
373
-	}
374
-
375
-	/**
376
-	 * Get the source path (on disk) of a given path
377
-	 *
378
-	 * @param string $path
379
-	 * @return string
380
-	 * @throws ForbiddenException
381
-	 */
382
-	public function getSourcePath($path) {
383
-		if (Filesystem::isFileBlacklisted($path)) {
384
-			throw new ForbiddenException('Invalid path', false);
385
-		}
386
-
387
-		$fullPath = $this->datadir . $path;
388
-		$currentPath = $path;
389
-		if ($this->allowSymlinks || $currentPath === '') {
390
-			return $fullPath;
391
-		}
392
-		$pathToResolve = $fullPath;
393
-		$realPath = realpath($pathToResolve);
394
-		while ($realPath === false) { // for non existing files check the parent directory
395
-			$currentPath = dirname($currentPath);
396
-			if ($currentPath === '' || $currentPath === '.') {
397
-				return $fullPath;
398
-			}
399
-			$realPath = realpath($this->datadir . $currentPath);
400
-		}
401
-		if ($realPath) {
402
-			$realPath = $realPath . '/';
403
-		}
404
-		if (substr($realPath, 0, $this->dataDirLength) === $this->realDataDir) {
405
-			return $fullPath;
406
-		}
407
-
408
-		\OCP\Util::writeLog('core', "Following symlinks is not allowed ('$fullPath' -> '$realPath' not inside '{$this->realDataDir}')", ILogger::ERROR);
409
-		throw new ForbiddenException('Following symlinks is not allowed', false);
410
-	}
411
-
412
-	/**
413
-	 * {@inheritdoc}
414
-	 */
415
-	public function isLocal() {
416
-		return true;
417
-	}
418
-
419
-	/**
420
-	 * get the ETag for a file or folder
421
-	 *
422
-	 * @param string $path
423
-	 * @return string
424
-	 */
425
-	public function getETag($path) {
426
-		if ($this->is_file($path)) {
427
-			$stat = $this->stat($path);
428
-
429
-			if ($stat === false) {
430
-				return md5('');
431
-			}
432
-
433
-			$toHash = '';
434
-			if (isset($stat['mtime'])) {
435
-				$toHash .= $stat['mtime'];
436
-			}
437
-			if (isset($stat['ino'])) {
438
-				$toHash .= $stat['ino'];
439
-			}
440
-			if (isset($stat['dev'])) {
441
-				$toHash .= $stat['dev'];
442
-			}
443
-			if (isset($stat['size'])) {
444
-				$toHash .= $stat['size'];
445
-			}
446
-
447
-			return md5($toHash);
448
-		} else {
449
-			return parent::getETag($path);
450
-		}
451
-	}
452
-
453
-	/**
454
-	 * @param IStorage $sourceStorage
455
-	 * @param string $sourceInternalPath
456
-	 * @param string $targetInternalPath
457
-	 * @param bool $preserveMtime
458
-	 * @return bool
459
-	 */
460
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
461
-		if ($sourceStorage->instanceOfStorage(Local::class)) {
462
-			if ($sourceStorage->instanceOfStorage(Jail::class)) {
463
-				/**
464
-				 * @var \OC\Files\Storage\Wrapper\Jail $sourceStorage
465
-				 */
466
-				$sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
467
-			}
468
-			/**
469
-			 * @var \OC\Files\Storage\Local $sourceStorage
470
-			 */
471
-			$rootStorage = new Local(['datadir' => '/']);
472
-			return $rootStorage->copy($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
473
-		} else {
474
-			return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
475
-		}
476
-	}
477
-
478
-	/**
479
-	 * @param IStorage $sourceStorage
480
-	 * @param string $sourceInternalPath
481
-	 * @param string $targetInternalPath
482
-	 * @return bool
483
-	 */
484
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
485
-		if ($sourceStorage->instanceOfStorage(Local::class)) {
486
-			if ($sourceStorage->instanceOfStorage(Jail::class)) {
487
-				/**
488
-				 * @var \OC\Files\Storage\Wrapper\Jail $sourceStorage
489
-				 */
490
-				$sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
491
-			}
492
-			/**
493
-			 * @var \OC\Files\Storage\Local $sourceStorage
494
-			 */
495
-			$rootStorage = new Local(['datadir' => '/']);
496
-			return $rootStorage->rename($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
497
-		} else {
498
-			return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
499
-		}
500
-	}
501
-
502
-	public function writeStream(string $path, $stream, int $size = null): int {
503
-		return (int)file_put_contents($this->getSourcePath($path), $stream);
504
-	}
53
+    protected $datadir;
54
+
55
+    protected $dataDirLength;
56
+
57
+    protected $allowSymlinks = false;
58
+
59
+    protected $realDataDir;
60
+
61
+    public function __construct($arguments) {
62
+        if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) {
63
+            throw new \InvalidArgumentException('No data directory set for local storage');
64
+        }
65
+        $this->datadir = str_replace('//', '/', $arguments['datadir']);
66
+        // some crazy code uses a local storage on root...
67
+        if ($this->datadir === '/') {
68
+            $this->realDataDir = $this->datadir;
69
+        } else {
70
+            $realPath = realpath($this->datadir) ?: $this->datadir;
71
+            $this->realDataDir = rtrim($realPath, '/') . '/';
72
+        }
73
+        if (substr($this->datadir, -1) !== '/') {
74
+            $this->datadir .= '/';
75
+        }
76
+        $this->dataDirLength = strlen($this->realDataDir);
77
+    }
78
+
79
+    public function __destruct() {
80
+    }
81
+
82
+    public function getId() {
83
+        return 'local::' . $this->datadir;
84
+    }
85
+
86
+    public function mkdir($path) {
87
+        return @mkdir($this->getSourcePath($path), 0777, true);
88
+    }
89
+
90
+    public function rmdir($path) {
91
+        if (!$this->isDeletable($path)) {
92
+            return false;
93
+        }
94
+        try {
95
+            $it = new \RecursiveIteratorIterator(
96
+                new \RecursiveDirectoryIterator($this->getSourcePath($path)),
97
+                \RecursiveIteratorIterator::CHILD_FIRST
98
+            );
99
+            /**
100
+             * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
101
+             * This bug is fixed in PHP 5.5.9 or before
102
+             * See #8376
103
+             */
104
+            $it->rewind();
105
+            while ($it->valid()) {
106
+                /**
107
+                 * @var \SplFileInfo $file
108
+                 */
109
+                $file = $it->current();
110
+                if (in_array($file->getBasename(), ['.', '..'])) {
111
+                    $it->next();
112
+                    continue;
113
+                } elseif ($file->isDir()) {
114
+                    rmdir($file->getPathname());
115
+                } elseif ($file->isFile() || $file->isLink()) {
116
+                    unlink($file->getPathname());
117
+                }
118
+                $it->next();
119
+            }
120
+            return rmdir($this->getSourcePath($path));
121
+        } catch (\UnexpectedValueException $e) {
122
+            return false;
123
+        }
124
+    }
125
+
126
+    public function opendir($path) {
127
+        return opendir($this->getSourcePath($path));
128
+    }
129
+
130
+    public function is_dir($path) {
131
+        if (substr($path, -1) == '/') {
132
+            $path = substr($path, 0, -1);
133
+        }
134
+        return is_dir($this->getSourcePath($path));
135
+    }
136
+
137
+    public function is_file($path) {
138
+        return is_file($this->getSourcePath($path));
139
+    }
140
+
141
+    public function stat($path) {
142
+        clearstatcache();
143
+        $fullPath = $this->getSourcePath($path);
144
+        $statResult = stat($fullPath);
145
+        if (PHP_INT_SIZE === 4 && !$this->is_dir($path)) {
146
+            $filesize = $this->filesize($path);
147
+            $statResult['size'] = $filesize;
148
+            $statResult[7] = $filesize;
149
+        }
150
+        return $statResult;
151
+    }
152
+
153
+    public function filetype($path) {
154
+        $filetype = filetype($this->getSourcePath($path));
155
+        if ($filetype == 'link') {
156
+            $filetype = filetype(realpath($this->getSourcePath($path)));
157
+        }
158
+        return $filetype;
159
+    }
160
+
161
+    public function filesize($path) {
162
+        if ($this->is_dir($path)) {
163
+            return 0;
164
+        }
165
+        $fullPath = $this->getSourcePath($path);
166
+        if (PHP_INT_SIZE === 4) {
167
+            $helper = new \OC\LargeFileHelper;
168
+            return $helper->getFileSize($fullPath);
169
+        }
170
+        return filesize($fullPath);
171
+    }
172
+
173
+    public function isReadable($path) {
174
+        return is_readable($this->getSourcePath($path));
175
+    }
176
+
177
+    public function isUpdatable($path) {
178
+        return is_writable($this->getSourcePath($path));
179
+    }
180
+
181
+    public function file_exists($path) {
182
+        return file_exists($this->getSourcePath($path));
183
+    }
184
+
185
+    public function filemtime($path) {
186
+        $fullPath = $this->getSourcePath($path);
187
+        clearstatcache(true, $fullPath);
188
+        if (!$this->file_exists($path)) {
189
+            return false;
190
+        }
191
+        if (PHP_INT_SIZE === 4) {
192
+            $helper = new \OC\LargeFileHelper();
193
+            return $helper->getFileMtime($fullPath);
194
+        }
195
+        return filemtime($fullPath);
196
+    }
197
+
198
+    public function touch($path, $mtime = null) {
199
+        // sets the modification time of the file to the given value.
200
+        // If mtime is nil the current time is set.
201
+        // note that the access time of the file always changes to the current time.
202
+        if ($this->file_exists($path) and !$this->isUpdatable($path)) {
203
+            return false;
204
+        }
205
+        if (!is_null($mtime)) {
206
+            $result = @touch($this->getSourcePath($path), $mtime);
207
+        } else {
208
+            $result = @touch($this->getSourcePath($path));
209
+        }
210
+        if ($result) {
211
+            clearstatcache(true, $this->getSourcePath($path));
212
+        }
213
+
214
+        return $result;
215
+    }
216
+
217
+    public function file_get_contents($path) {
218
+        return file_get_contents($this->getSourcePath($path));
219
+    }
220
+
221
+    public function file_put_contents($path, $data) {
222
+        return file_put_contents($this->getSourcePath($path), $data);
223
+    }
224
+
225
+    public function unlink($path) {
226
+        if ($this->is_dir($path)) {
227
+            return $this->rmdir($path);
228
+        } else if ($this->is_file($path)) {
229
+            return unlink($this->getSourcePath($path));
230
+        } else {
231
+            return false;
232
+        }
233
+
234
+    }
235
+
236
+    private function treeContainsBlacklistedFile(string $path): bool {
237
+        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path));
238
+        foreach ($iterator as $file) {
239
+            /** @var \SplFileInfo $file */
240
+            if (Filesystem::isFileBlacklisted($file->getBasename())) {
241
+                return true;
242
+            }
243
+        }
244
+
245
+        return false;
246
+    }
247
+
248
+    public function rename($path1, $path2) {
249
+        $srcParent = dirname($path1);
250
+        $dstParent = dirname($path2);
251
+
252
+        if (!$this->isUpdatable($srcParent)) {
253
+            \OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : ' . $srcParent, ILogger::ERROR);
254
+            return false;
255
+        }
256
+
257
+        if (!$this->isUpdatable($dstParent)) {
258
+            \OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : ' . $dstParent, ILogger::ERROR);
259
+            return false;
260
+        }
261
+
262
+        if (!$this->file_exists($path1)) {
263
+            \OCP\Util::writeLog('core', 'unable to rename, file does not exists : ' . $path1, ILogger::ERROR);
264
+            return false;
265
+        }
266
+
267
+        if ($this->is_dir($path2)) {
268
+            $this->rmdir($path2);
269
+        } else if ($this->is_file($path2)) {
270
+            $this->unlink($path2);
271
+        }
272
+
273
+        if ($this->is_dir($path1)) {
274
+            // we can't move folders across devices, use copy instead
275
+            $stat1 = stat(dirname($this->getSourcePath($path1)));
276
+            $stat2 = stat(dirname($this->getSourcePath($path2)));
277
+            if ($stat1['dev'] !== $stat2['dev']) {
278
+                $result = $this->copy($path1, $path2);
279
+                if ($result) {
280
+                    $result &= $this->rmdir($path1);
281
+                }
282
+                return $result;
283
+            }
284
+
285
+            if ($this->treeContainsBlacklistedFile($this->getSourcePath($path1))) {
286
+                throw new ForbiddenException('Invalid path', false);
287
+            }
288
+        }
289
+
290
+        return rename($this->getSourcePath($path1), $this->getSourcePath($path2));
291
+    }
292
+
293
+    public function copy($path1, $path2) {
294
+        if ($this->is_dir($path1)) {
295
+            return parent::copy($path1, $path2);
296
+        } else {
297
+            return copy($this->getSourcePath($path1), $this->getSourcePath($path2));
298
+        }
299
+    }
300
+
301
+    public function fopen($path, $mode) {
302
+        return fopen($this->getSourcePath($path), $mode);
303
+    }
304
+
305
+    public function hash($type, $path, $raw = false) {
306
+        return hash_file($type, $this->getSourcePath($path), $raw);
307
+    }
308
+
309
+    public function free_space($path) {
310
+        $sourcePath = $this->getSourcePath($path);
311
+        // using !is_dir because $sourcePath might be a part file or
312
+        // non-existing file, so we'd still want to use the parent dir
313
+        // in such cases
314
+        if (!is_dir($sourcePath)) {
315
+            // disk_free_space doesn't work on files
316
+            $sourcePath = dirname($sourcePath);
317
+        }
318
+        $space = @disk_free_space($sourcePath);
319
+        if ($space === false || is_null($space)) {
320
+            return \OCP\Files\FileInfo::SPACE_UNKNOWN;
321
+        }
322
+        return $space;
323
+    }
324
+
325
+    public function search($query) {
326
+        return $this->searchInDir($query);
327
+    }
328
+
329
+    public function getLocalFile($path) {
330
+        return $this->getSourcePath($path);
331
+    }
332
+
333
+    public function getLocalFolder($path) {
334
+        return $this->getSourcePath($path);
335
+    }
336
+
337
+    /**
338
+     * @param string $query
339
+     * @param string $dir
340
+     * @return array
341
+     */
342
+    protected function searchInDir($query, $dir = '') {
343
+        $files = [];
344
+        $physicalDir = $this->getSourcePath($dir);
345
+        foreach (scandir($physicalDir) as $item) {
346
+            if (\OC\Files\Filesystem::isIgnoredDir($item))
347
+                continue;
348
+            $physicalItem = $physicalDir . '/' . $item;
349
+
350
+            if (strstr(strtolower($item), strtolower($query)) !== false) {
351
+                $files[] = $dir . '/' . $item;
352
+            }
353
+            if (is_dir($physicalItem)) {
354
+                $files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
355
+            }
356
+        }
357
+        return $files;
358
+    }
359
+
360
+    /**
361
+     * check if a file or folder has been updated since $time
362
+     *
363
+     * @param string $path
364
+     * @param int $time
365
+     * @return bool
366
+     */
367
+    public function hasUpdated($path, $time) {
368
+        if ($this->file_exists($path)) {
369
+            return $this->filemtime($path) > $time;
370
+        } else {
371
+            return true;
372
+        }
373
+    }
374
+
375
+    /**
376
+     * Get the source path (on disk) of a given path
377
+     *
378
+     * @param string $path
379
+     * @return string
380
+     * @throws ForbiddenException
381
+     */
382
+    public function getSourcePath($path) {
383
+        if (Filesystem::isFileBlacklisted($path)) {
384
+            throw new ForbiddenException('Invalid path', false);
385
+        }
386
+
387
+        $fullPath = $this->datadir . $path;
388
+        $currentPath = $path;
389
+        if ($this->allowSymlinks || $currentPath === '') {
390
+            return $fullPath;
391
+        }
392
+        $pathToResolve = $fullPath;
393
+        $realPath = realpath($pathToResolve);
394
+        while ($realPath === false) { // for non existing files check the parent directory
395
+            $currentPath = dirname($currentPath);
396
+            if ($currentPath === '' || $currentPath === '.') {
397
+                return $fullPath;
398
+            }
399
+            $realPath = realpath($this->datadir . $currentPath);
400
+        }
401
+        if ($realPath) {
402
+            $realPath = $realPath . '/';
403
+        }
404
+        if (substr($realPath, 0, $this->dataDirLength) === $this->realDataDir) {
405
+            return $fullPath;
406
+        }
407
+
408
+        \OCP\Util::writeLog('core', "Following symlinks is not allowed ('$fullPath' -> '$realPath' not inside '{$this->realDataDir}')", ILogger::ERROR);
409
+        throw new ForbiddenException('Following symlinks is not allowed', false);
410
+    }
411
+
412
+    /**
413
+     * {@inheritdoc}
414
+     */
415
+    public function isLocal() {
416
+        return true;
417
+    }
418
+
419
+    /**
420
+     * get the ETag for a file or folder
421
+     *
422
+     * @param string $path
423
+     * @return string
424
+     */
425
+    public function getETag($path) {
426
+        if ($this->is_file($path)) {
427
+            $stat = $this->stat($path);
428
+
429
+            if ($stat === false) {
430
+                return md5('');
431
+            }
432
+
433
+            $toHash = '';
434
+            if (isset($stat['mtime'])) {
435
+                $toHash .= $stat['mtime'];
436
+            }
437
+            if (isset($stat['ino'])) {
438
+                $toHash .= $stat['ino'];
439
+            }
440
+            if (isset($stat['dev'])) {
441
+                $toHash .= $stat['dev'];
442
+            }
443
+            if (isset($stat['size'])) {
444
+                $toHash .= $stat['size'];
445
+            }
446
+
447
+            return md5($toHash);
448
+        } else {
449
+            return parent::getETag($path);
450
+        }
451
+    }
452
+
453
+    /**
454
+     * @param IStorage $sourceStorage
455
+     * @param string $sourceInternalPath
456
+     * @param string $targetInternalPath
457
+     * @param bool $preserveMtime
458
+     * @return bool
459
+     */
460
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
461
+        if ($sourceStorage->instanceOfStorage(Local::class)) {
462
+            if ($sourceStorage->instanceOfStorage(Jail::class)) {
463
+                /**
464
+                 * @var \OC\Files\Storage\Wrapper\Jail $sourceStorage
465
+                 */
466
+                $sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
467
+            }
468
+            /**
469
+             * @var \OC\Files\Storage\Local $sourceStorage
470
+             */
471
+            $rootStorage = new Local(['datadir' => '/']);
472
+            return $rootStorage->copy($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
473
+        } else {
474
+            return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
475
+        }
476
+    }
477
+
478
+    /**
479
+     * @param IStorage $sourceStorage
480
+     * @param string $sourceInternalPath
481
+     * @param string $targetInternalPath
482
+     * @return bool
483
+     */
484
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
485
+        if ($sourceStorage->instanceOfStorage(Local::class)) {
486
+            if ($sourceStorage->instanceOfStorage(Jail::class)) {
487
+                /**
488
+                 * @var \OC\Files\Storage\Wrapper\Jail $sourceStorage
489
+                 */
490
+                $sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
491
+            }
492
+            /**
493
+             * @var \OC\Files\Storage\Local $sourceStorage
494
+             */
495
+            $rootStorage = new Local(['datadir' => '/']);
496
+            return $rootStorage->rename($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
497
+        } else {
498
+            return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
499
+        }
500
+    }
501
+
502
+    public function writeStream(string $path, $stream, int $size = null): int {
503
+        return (int)file_put_contents($this->getSourcePath($path), $stream);
504
+    }
505 505
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/Temporary.php 1 patch
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(['datadir' => \OC::$server->getTempManager()->getTemporaryFolder()]);
34
-	}
32
+    public function __construct($arguments = null) {
33
+        parent::__construct(['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.
lib/private/Files/Storage/Common.php 1 patch
Indentation   +782 added lines, -782 removed lines patch added patch discarded remove patch
@@ -76,790 +76,790 @@
 block discarded – undo
76 76
  */
77 77
 abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
78 78
 
79
-	use LocalTempFileTrait;
80
-
81
-	protected $cache;
82
-	protected $scanner;
83
-	protected $watcher;
84
-	protected $propagator;
85
-	protected $storageCache;
86
-	protected $updater;
87
-
88
-	protected $mountOptions = [];
89
-	protected $owner = null;
90
-
91
-	private $shouldLogLocks = null;
92
-	private $logger;
93
-
94
-	public function __construct($parameters) {
95
-	}
96
-
97
-	/**
98
-	 * Remove a file or folder
99
-	 *
100
-	 * @param string $path
101
-	 * @return bool
102
-	 */
103
-	protected function remove($path) {
104
-		if ($this->is_dir($path)) {
105
-			return $this->rmdir($path);
106
-		} else if ($this->is_file($path)) {
107
-			return $this->unlink($path);
108
-		} else {
109
-			return false;
110
-		}
111
-	}
112
-
113
-	public function is_dir($path) {
114
-		return $this->filetype($path) === 'dir';
115
-	}
116
-
117
-	public function is_file($path) {
118
-		return $this->filetype($path) === 'file';
119
-	}
120
-
121
-	public function filesize($path) {
122
-		if ($this->is_dir($path)) {
123
-			return 0; //by definition
124
-		} else {
125
-			$stat = $this->stat($path);
126
-			if (isset($stat['size'])) {
127
-				return $stat['size'];
128
-			} else {
129
-				return 0;
130
-			}
131
-		}
132
-	}
133
-
134
-	public function isReadable($path) {
135
-		// at least check whether it exists
136
-		// subclasses might want to implement this more thoroughly
137
-		return $this->file_exists($path);
138
-	}
139
-
140
-	public function isUpdatable($path) {
141
-		// at least check whether it exists
142
-		// subclasses might want to implement this more thoroughly
143
-		// a non-existing file/folder isn't updatable
144
-		return $this->file_exists($path);
145
-	}
146
-
147
-	public function isCreatable($path) {
148
-		if ($this->is_dir($path) && $this->isUpdatable($path)) {
149
-			return true;
150
-		}
151
-		return false;
152
-	}
153
-
154
-	public function isDeletable($path) {
155
-		if ($path === '' || $path === '/') {
156
-			return false;
157
-		}
158
-		$parent = dirname($path);
159
-		return $this->isUpdatable($parent) && $this->isUpdatable($path);
160
-	}
161
-
162
-	public function isSharable($path) {
163
-		return $this->isReadable($path);
164
-	}
165
-
166
-	public function getPermissions($path) {
167
-		$permissions = 0;
168
-		if ($this->isCreatable($path)) {
169
-			$permissions |= \OCP\Constants::PERMISSION_CREATE;
170
-		}
171
-		if ($this->isReadable($path)) {
172
-			$permissions |= \OCP\Constants::PERMISSION_READ;
173
-		}
174
-		if ($this->isUpdatable($path)) {
175
-			$permissions |= \OCP\Constants::PERMISSION_UPDATE;
176
-		}
177
-		if ($this->isDeletable($path)) {
178
-			$permissions |= \OCP\Constants::PERMISSION_DELETE;
179
-		}
180
-		if ($this->isSharable($path)) {
181
-			$permissions |= \OCP\Constants::PERMISSION_SHARE;
182
-		}
183
-		return $permissions;
184
-	}
185
-
186
-	public function filemtime($path) {
187
-		$stat = $this->stat($path);
188
-		if (isset($stat['mtime']) && $stat['mtime'] > 0) {
189
-			return $stat['mtime'];
190
-		} else {
191
-			return 0;
192
-		}
193
-	}
194
-
195
-	public function file_get_contents($path) {
196
-		$handle = $this->fopen($path, "r");
197
-		if (!$handle) {
198
-			return false;
199
-		}
200
-		$data = stream_get_contents($handle);
201
-		fclose($handle);
202
-		return $data;
203
-	}
204
-
205
-	public function file_put_contents($path, $data) {
206
-		$handle = $this->fopen($path, "w");
207
-		$this->removeCachedFile($path);
208
-		$count = fwrite($handle, $data);
209
-		fclose($handle);
210
-		return $count;
211
-	}
212
-
213
-	public function rename($path1, $path2) {
214
-		$this->remove($path2);
215
-
216
-		$this->removeCachedFile($path1);
217
-		return $this->copy($path1, $path2) and $this->remove($path1);
218
-	}
219
-
220
-	public function copy($path1, $path2) {
221
-		if ($this->is_dir($path1)) {
222
-			$this->remove($path2);
223
-			$dir = $this->opendir($path1);
224
-			$this->mkdir($path2);
225
-			while ($file = readdir($dir)) {
226
-				if (!Filesystem::isIgnoredDir($file)) {
227
-					if (!$this->copy($path1 . '/' . $file, $path2 . '/' . $file)) {
228
-						return false;
229
-					}
230
-				}
231
-			}
232
-			closedir($dir);
233
-			return true;
234
-		} else {
235
-			$source = $this->fopen($path1, 'r');
236
-			$target = $this->fopen($path2, 'w');
237
-			list(, $result) = \OC_Helper::streamCopy($source, $target);
238
-			if (!$result) {
239
-				\OC::$server->getLogger()->warning("Failed to write data while copying $path1 to $path2");
240
-			}
241
-			$this->removeCachedFile($path2);
242
-			return $result;
243
-		}
244
-	}
245
-
246
-	public function getMimeType($path) {
247
-		if ($this->is_dir($path)) {
248
-			return 'httpd/unix-directory';
249
-		} elseif ($this->file_exists($path)) {
250
-			return \OC::$server->getMimeTypeDetector()->detectPath($path);
251
-		} else {
252
-			return false;
253
-		}
254
-	}
255
-
256
-	public function hash($type, $path, $raw = false) {
257
-		$fh = $this->fopen($path, 'rb');
258
-		$ctx = hash_init($type);
259
-		hash_update_stream($ctx, $fh);
260
-		fclose($fh);
261
-		return hash_final($ctx, $raw);
262
-	}
263
-
264
-	public function search($query) {
265
-		return $this->searchInDir($query);
266
-	}
267
-
268
-	public function getLocalFile($path) {
269
-		return $this->getCachedFile($path);
270
-	}
271
-
272
-	/**
273
-	 * @param string $path
274
-	 * @param string $target
275
-	 */
276
-	private function addLocalFolder($path, $target) {
277
-		$dh = $this->opendir($path);
278
-		if (is_resource($dh)) {
279
-			while (($file = readdir($dh)) !== false) {
280
-				if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
281
-					if ($this->is_dir($path . '/' . $file)) {
282
-						mkdir($target . '/' . $file);
283
-						$this->addLocalFolder($path . '/' . $file, $target . '/' . $file);
284
-					} else {
285
-						$tmp = $this->toTmpFile($path . '/' . $file);
286
-						rename($tmp, $target . '/' . $file);
287
-					}
288
-				}
289
-			}
290
-		}
291
-	}
292
-
293
-	/**
294
-	 * @param string $query
295
-	 * @param string $dir
296
-	 * @return array
297
-	 */
298
-	protected function searchInDir($query, $dir = '') {
299
-		$files = [];
300
-		$dh = $this->opendir($dir);
301
-		if (is_resource($dh)) {
302
-			while (($item = readdir($dh)) !== false) {
303
-				if (\OC\Files\Filesystem::isIgnoredDir($item)) continue;
304
-				if (strstr(strtolower($item), strtolower($query)) !== false) {
305
-					$files[] = $dir . '/' . $item;
306
-				}
307
-				if ($this->is_dir($dir . '/' . $item)) {
308
-					$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
309
-				}
310
-			}
311
-		}
312
-		closedir($dh);
313
-		return $files;
314
-	}
315
-
316
-	/**
317
-	 * check if a file or folder has been updated since $time
318
-	 *
319
-	 * The method is only used to check if the cache needs to be updated. Storage backends that don't support checking
320
-	 * the mtime should always return false here. As a result storage implementations that always return false expect
321
-	 * exclusive access to the backend and will not pick up files that have been added in a way that circumvents
322
-	 * ownClouds filesystem.
323
-	 *
324
-	 * @param string $path
325
-	 * @param int $time
326
-	 * @return bool
327
-	 */
328
-	public function hasUpdated($path, $time) {
329
-		return $this->filemtime($path) > $time;
330
-	}
331
-
332
-	public function getCache($path = '', $storage = null) {
333
-		if (!$storage) {
334
-			$storage = $this;
335
-		}
336
-		if (!isset($storage->cache)) {
337
-			$storage->cache = new Cache($storage);
338
-		}
339
-		return $storage->cache;
340
-	}
341
-
342
-	public function getScanner($path = '', $storage = null) {
343
-		if (!$storage) {
344
-			$storage = $this;
345
-		}
346
-		if (!isset($storage->scanner)) {
347
-			$storage->scanner = new Scanner($storage);
348
-		}
349
-		return $storage->scanner;
350
-	}
351
-
352
-	public function getWatcher($path = '', $storage = null) {
353
-		if (!$storage) {
354
-			$storage = $this;
355
-		}
356
-		if (!isset($this->watcher)) {
357
-			$this->watcher = new Watcher($storage);
358
-			$globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER);
359
-			$this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy));
360
-		}
361
-		return $this->watcher;
362
-	}
363
-
364
-	/**
365
-	 * get a propagator instance for the cache
366
-	 *
367
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
368
-	 * @return \OC\Files\Cache\Propagator
369
-	 */
370
-	public function getPropagator($storage = null) {
371
-		if (!$storage) {
372
-			$storage = $this;
373
-		}
374
-		if (!isset($storage->propagator)) {
375
-			$config = \OC::$server->getSystemConfig();
376
-			$storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection(), ['appdata_' . $config->getValue('instanceid')]);
377
-		}
378
-		return $storage->propagator;
379
-	}
380
-
381
-	public function getUpdater($storage = null) {
382
-		if (!$storage) {
383
-			$storage = $this;
384
-		}
385
-		if (!isset($storage->updater)) {
386
-			$storage->updater = new Updater($storage);
387
-		}
388
-		return $storage->updater;
389
-	}
390
-
391
-	public function getStorageCache($storage = null) {
392
-		if (!$storage) {
393
-			$storage = $this;
394
-		}
395
-		if (!isset($this->storageCache)) {
396
-			$this->storageCache = new \OC\Files\Cache\Storage($storage);
397
-		}
398
-		return $this->storageCache;
399
-	}
400
-
401
-	/**
402
-	 * get the owner of a path
403
-	 *
404
-	 * @param string $path The path to get the owner
405
-	 * @return string|false uid or false
406
-	 */
407
-	public function getOwner($path) {
408
-		if ($this->owner === null) {
409
-			$this->owner = \OC_User::getUser();
410
-		}
411
-
412
-		return $this->owner;
413
-	}
414
-
415
-	/**
416
-	 * get the ETag for a file or folder
417
-	 *
418
-	 * @param string $path
419
-	 * @return string
420
-	 */
421
-	public function getETag($path) {
422
-		return uniqid();
423
-	}
424
-
425
-	/**
426
-	 * clean a path, i.e. remove all redundant '.' and '..'
427
-	 * making sure that it can't point to higher than '/'
428
-	 *
429
-	 * @param string $path The path to clean
430
-	 * @return string cleaned path
431
-	 */
432
-	public function cleanPath($path) {
433
-		if (strlen($path) == 0 or $path[0] != '/') {
434
-			$path = '/' . $path;
435
-		}
436
-
437
-		$output = [];
438
-		foreach (explode('/', $path) as $chunk) {
439
-			if ($chunk == '..') {
440
-				array_pop($output);
441
-			} else if ($chunk == '.') {
442
-			} else {
443
-				$output[] = $chunk;
444
-			}
445
-		}
446
-		return implode('/', $output);
447
-	}
448
-
449
-	/**
450
-	 * Test a storage for availability
451
-	 *
452
-	 * @return bool
453
-	 */
454
-	public function test() {
455
-		try {
456
-			if ($this->stat('')) {
457
-				return true;
458
-			}
459
-			\OC::$server->getLogger()->info("External storage not available: stat() failed");
460
-			return false;
461
-		} catch (\Exception $e) {
462
-			\OC::$server->getLogger()->info("External storage not available: " . $e->getMessage());
463
-			\OC::$server->getLogger()->logException($e, ['level' => ILogger::DEBUG]);
464
-			return false;
465
-		}
466
-	}
467
-
468
-	/**
469
-	 * get the free space in the storage
470
-	 *
471
-	 * @param string $path
472
-	 * @return int|false
473
-	 */
474
-	public function free_space($path) {
475
-		return \OCP\Files\FileInfo::SPACE_UNKNOWN;
476
-	}
477
-
478
-	/**
479
-	 * {@inheritdoc}
480
-	 */
481
-	public function isLocal() {
482
-		// the common implementation returns a temporary file by
483
-		// default, which is not local
484
-		return false;
485
-	}
486
-
487
-	/**
488
-	 * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
489
-	 *
490
-	 * @param string $class
491
-	 * @return bool
492
-	 */
493
-	public function instanceOfStorage($class) {
494
-		if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
495
-			// FIXME Temporary fix to keep existing checks working
496
-			$class = '\OCA\Files_Sharing\SharedStorage';
497
-		}
498
-		return is_a($this, $class);
499
-	}
500
-
501
-	/**
502
-	 * A custom storage implementation can return an url for direct download of a give file.
503
-	 *
504
-	 * For now the returned array can hold the parameter url - in future more attributes might follow.
505
-	 *
506
-	 * @param string $path
507
-	 * @return array|false
508
-	 */
509
-	public function getDirectDownload($path) {
510
-		return [];
511
-	}
512
-
513
-	/**
514
-	 * @inheritdoc
515
-	 * @throws InvalidPathException
516
-	 */
517
-	public function verifyPath($path, $fileName) {
518
-
519
-		// verify empty and dot files
520
-		$trimmed = trim($fileName);
521
-		if ($trimmed === '') {
522
-			throw new EmptyFileNameException();
523
-		}
524
-
525
-		if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
526
-			throw new InvalidDirectoryException();
527
-		}
528
-
529
-		if (!\OC::$server->getDatabaseConnection()->supports4ByteText()) {
530
-			// verify database - e.g. mysql only 3-byte chars
531
-			if (preg_match('%(?:
79
+    use LocalTempFileTrait;
80
+
81
+    protected $cache;
82
+    protected $scanner;
83
+    protected $watcher;
84
+    protected $propagator;
85
+    protected $storageCache;
86
+    protected $updater;
87
+
88
+    protected $mountOptions = [];
89
+    protected $owner = null;
90
+
91
+    private $shouldLogLocks = null;
92
+    private $logger;
93
+
94
+    public function __construct($parameters) {
95
+    }
96
+
97
+    /**
98
+     * Remove a file or folder
99
+     *
100
+     * @param string $path
101
+     * @return bool
102
+     */
103
+    protected function remove($path) {
104
+        if ($this->is_dir($path)) {
105
+            return $this->rmdir($path);
106
+        } else if ($this->is_file($path)) {
107
+            return $this->unlink($path);
108
+        } else {
109
+            return false;
110
+        }
111
+    }
112
+
113
+    public function is_dir($path) {
114
+        return $this->filetype($path) === 'dir';
115
+    }
116
+
117
+    public function is_file($path) {
118
+        return $this->filetype($path) === 'file';
119
+    }
120
+
121
+    public function filesize($path) {
122
+        if ($this->is_dir($path)) {
123
+            return 0; //by definition
124
+        } else {
125
+            $stat = $this->stat($path);
126
+            if (isset($stat['size'])) {
127
+                return $stat['size'];
128
+            } else {
129
+                return 0;
130
+            }
131
+        }
132
+    }
133
+
134
+    public function isReadable($path) {
135
+        // at least check whether it exists
136
+        // subclasses might want to implement this more thoroughly
137
+        return $this->file_exists($path);
138
+    }
139
+
140
+    public function isUpdatable($path) {
141
+        // at least check whether it exists
142
+        // subclasses might want to implement this more thoroughly
143
+        // a non-existing file/folder isn't updatable
144
+        return $this->file_exists($path);
145
+    }
146
+
147
+    public function isCreatable($path) {
148
+        if ($this->is_dir($path) && $this->isUpdatable($path)) {
149
+            return true;
150
+        }
151
+        return false;
152
+    }
153
+
154
+    public function isDeletable($path) {
155
+        if ($path === '' || $path === '/') {
156
+            return false;
157
+        }
158
+        $parent = dirname($path);
159
+        return $this->isUpdatable($parent) && $this->isUpdatable($path);
160
+    }
161
+
162
+    public function isSharable($path) {
163
+        return $this->isReadable($path);
164
+    }
165
+
166
+    public function getPermissions($path) {
167
+        $permissions = 0;
168
+        if ($this->isCreatable($path)) {
169
+            $permissions |= \OCP\Constants::PERMISSION_CREATE;
170
+        }
171
+        if ($this->isReadable($path)) {
172
+            $permissions |= \OCP\Constants::PERMISSION_READ;
173
+        }
174
+        if ($this->isUpdatable($path)) {
175
+            $permissions |= \OCP\Constants::PERMISSION_UPDATE;
176
+        }
177
+        if ($this->isDeletable($path)) {
178
+            $permissions |= \OCP\Constants::PERMISSION_DELETE;
179
+        }
180
+        if ($this->isSharable($path)) {
181
+            $permissions |= \OCP\Constants::PERMISSION_SHARE;
182
+        }
183
+        return $permissions;
184
+    }
185
+
186
+    public function filemtime($path) {
187
+        $stat = $this->stat($path);
188
+        if (isset($stat['mtime']) && $stat['mtime'] > 0) {
189
+            return $stat['mtime'];
190
+        } else {
191
+            return 0;
192
+        }
193
+    }
194
+
195
+    public function file_get_contents($path) {
196
+        $handle = $this->fopen($path, "r");
197
+        if (!$handle) {
198
+            return false;
199
+        }
200
+        $data = stream_get_contents($handle);
201
+        fclose($handle);
202
+        return $data;
203
+    }
204
+
205
+    public function file_put_contents($path, $data) {
206
+        $handle = $this->fopen($path, "w");
207
+        $this->removeCachedFile($path);
208
+        $count = fwrite($handle, $data);
209
+        fclose($handle);
210
+        return $count;
211
+    }
212
+
213
+    public function rename($path1, $path2) {
214
+        $this->remove($path2);
215
+
216
+        $this->removeCachedFile($path1);
217
+        return $this->copy($path1, $path2) and $this->remove($path1);
218
+    }
219
+
220
+    public function copy($path1, $path2) {
221
+        if ($this->is_dir($path1)) {
222
+            $this->remove($path2);
223
+            $dir = $this->opendir($path1);
224
+            $this->mkdir($path2);
225
+            while ($file = readdir($dir)) {
226
+                if (!Filesystem::isIgnoredDir($file)) {
227
+                    if (!$this->copy($path1 . '/' . $file, $path2 . '/' . $file)) {
228
+                        return false;
229
+                    }
230
+                }
231
+            }
232
+            closedir($dir);
233
+            return true;
234
+        } else {
235
+            $source = $this->fopen($path1, 'r');
236
+            $target = $this->fopen($path2, 'w');
237
+            list(, $result) = \OC_Helper::streamCopy($source, $target);
238
+            if (!$result) {
239
+                \OC::$server->getLogger()->warning("Failed to write data while copying $path1 to $path2");
240
+            }
241
+            $this->removeCachedFile($path2);
242
+            return $result;
243
+        }
244
+    }
245
+
246
+    public function getMimeType($path) {
247
+        if ($this->is_dir($path)) {
248
+            return 'httpd/unix-directory';
249
+        } elseif ($this->file_exists($path)) {
250
+            return \OC::$server->getMimeTypeDetector()->detectPath($path);
251
+        } else {
252
+            return false;
253
+        }
254
+    }
255
+
256
+    public function hash($type, $path, $raw = false) {
257
+        $fh = $this->fopen($path, 'rb');
258
+        $ctx = hash_init($type);
259
+        hash_update_stream($ctx, $fh);
260
+        fclose($fh);
261
+        return hash_final($ctx, $raw);
262
+    }
263
+
264
+    public function search($query) {
265
+        return $this->searchInDir($query);
266
+    }
267
+
268
+    public function getLocalFile($path) {
269
+        return $this->getCachedFile($path);
270
+    }
271
+
272
+    /**
273
+     * @param string $path
274
+     * @param string $target
275
+     */
276
+    private function addLocalFolder($path, $target) {
277
+        $dh = $this->opendir($path);
278
+        if (is_resource($dh)) {
279
+            while (($file = readdir($dh)) !== false) {
280
+                if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
281
+                    if ($this->is_dir($path . '/' . $file)) {
282
+                        mkdir($target . '/' . $file);
283
+                        $this->addLocalFolder($path . '/' . $file, $target . '/' . $file);
284
+                    } else {
285
+                        $tmp = $this->toTmpFile($path . '/' . $file);
286
+                        rename($tmp, $target . '/' . $file);
287
+                    }
288
+                }
289
+            }
290
+        }
291
+    }
292
+
293
+    /**
294
+     * @param string $query
295
+     * @param string $dir
296
+     * @return array
297
+     */
298
+    protected function searchInDir($query, $dir = '') {
299
+        $files = [];
300
+        $dh = $this->opendir($dir);
301
+        if (is_resource($dh)) {
302
+            while (($item = readdir($dh)) !== false) {
303
+                if (\OC\Files\Filesystem::isIgnoredDir($item)) continue;
304
+                if (strstr(strtolower($item), strtolower($query)) !== false) {
305
+                    $files[] = $dir . '/' . $item;
306
+                }
307
+                if ($this->is_dir($dir . '/' . $item)) {
308
+                    $files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
309
+                }
310
+            }
311
+        }
312
+        closedir($dh);
313
+        return $files;
314
+    }
315
+
316
+    /**
317
+     * check if a file or folder has been updated since $time
318
+     *
319
+     * The method is only used to check if the cache needs to be updated. Storage backends that don't support checking
320
+     * the mtime should always return false here. As a result storage implementations that always return false expect
321
+     * exclusive access to the backend and will not pick up files that have been added in a way that circumvents
322
+     * ownClouds filesystem.
323
+     *
324
+     * @param string $path
325
+     * @param int $time
326
+     * @return bool
327
+     */
328
+    public function hasUpdated($path, $time) {
329
+        return $this->filemtime($path) > $time;
330
+    }
331
+
332
+    public function getCache($path = '', $storage = null) {
333
+        if (!$storage) {
334
+            $storage = $this;
335
+        }
336
+        if (!isset($storage->cache)) {
337
+            $storage->cache = new Cache($storage);
338
+        }
339
+        return $storage->cache;
340
+    }
341
+
342
+    public function getScanner($path = '', $storage = null) {
343
+        if (!$storage) {
344
+            $storage = $this;
345
+        }
346
+        if (!isset($storage->scanner)) {
347
+            $storage->scanner = new Scanner($storage);
348
+        }
349
+        return $storage->scanner;
350
+    }
351
+
352
+    public function getWatcher($path = '', $storage = null) {
353
+        if (!$storage) {
354
+            $storage = $this;
355
+        }
356
+        if (!isset($this->watcher)) {
357
+            $this->watcher = new Watcher($storage);
358
+            $globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER);
359
+            $this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy));
360
+        }
361
+        return $this->watcher;
362
+    }
363
+
364
+    /**
365
+     * get a propagator instance for the cache
366
+     *
367
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
368
+     * @return \OC\Files\Cache\Propagator
369
+     */
370
+    public function getPropagator($storage = null) {
371
+        if (!$storage) {
372
+            $storage = $this;
373
+        }
374
+        if (!isset($storage->propagator)) {
375
+            $config = \OC::$server->getSystemConfig();
376
+            $storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection(), ['appdata_' . $config->getValue('instanceid')]);
377
+        }
378
+        return $storage->propagator;
379
+    }
380
+
381
+    public function getUpdater($storage = null) {
382
+        if (!$storage) {
383
+            $storage = $this;
384
+        }
385
+        if (!isset($storage->updater)) {
386
+            $storage->updater = new Updater($storage);
387
+        }
388
+        return $storage->updater;
389
+    }
390
+
391
+    public function getStorageCache($storage = null) {
392
+        if (!$storage) {
393
+            $storage = $this;
394
+        }
395
+        if (!isset($this->storageCache)) {
396
+            $this->storageCache = new \OC\Files\Cache\Storage($storage);
397
+        }
398
+        return $this->storageCache;
399
+    }
400
+
401
+    /**
402
+     * get the owner of a path
403
+     *
404
+     * @param string $path The path to get the owner
405
+     * @return string|false uid or false
406
+     */
407
+    public function getOwner($path) {
408
+        if ($this->owner === null) {
409
+            $this->owner = \OC_User::getUser();
410
+        }
411
+
412
+        return $this->owner;
413
+    }
414
+
415
+    /**
416
+     * get the ETag for a file or folder
417
+     *
418
+     * @param string $path
419
+     * @return string
420
+     */
421
+    public function getETag($path) {
422
+        return uniqid();
423
+    }
424
+
425
+    /**
426
+     * clean a path, i.e. remove all redundant '.' and '..'
427
+     * making sure that it can't point to higher than '/'
428
+     *
429
+     * @param string $path The path to clean
430
+     * @return string cleaned path
431
+     */
432
+    public function cleanPath($path) {
433
+        if (strlen($path) == 0 or $path[0] != '/') {
434
+            $path = '/' . $path;
435
+        }
436
+
437
+        $output = [];
438
+        foreach (explode('/', $path) as $chunk) {
439
+            if ($chunk == '..') {
440
+                array_pop($output);
441
+            } else if ($chunk == '.') {
442
+            } else {
443
+                $output[] = $chunk;
444
+            }
445
+        }
446
+        return implode('/', $output);
447
+    }
448
+
449
+    /**
450
+     * Test a storage for availability
451
+     *
452
+     * @return bool
453
+     */
454
+    public function test() {
455
+        try {
456
+            if ($this->stat('')) {
457
+                return true;
458
+            }
459
+            \OC::$server->getLogger()->info("External storage not available: stat() failed");
460
+            return false;
461
+        } catch (\Exception $e) {
462
+            \OC::$server->getLogger()->info("External storage not available: " . $e->getMessage());
463
+            \OC::$server->getLogger()->logException($e, ['level' => ILogger::DEBUG]);
464
+            return false;
465
+        }
466
+    }
467
+
468
+    /**
469
+     * get the free space in the storage
470
+     *
471
+     * @param string $path
472
+     * @return int|false
473
+     */
474
+    public function free_space($path) {
475
+        return \OCP\Files\FileInfo::SPACE_UNKNOWN;
476
+    }
477
+
478
+    /**
479
+     * {@inheritdoc}
480
+     */
481
+    public function isLocal() {
482
+        // the common implementation returns a temporary file by
483
+        // default, which is not local
484
+        return false;
485
+    }
486
+
487
+    /**
488
+     * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
489
+     *
490
+     * @param string $class
491
+     * @return bool
492
+     */
493
+    public function instanceOfStorage($class) {
494
+        if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
495
+            // FIXME Temporary fix to keep existing checks working
496
+            $class = '\OCA\Files_Sharing\SharedStorage';
497
+        }
498
+        return is_a($this, $class);
499
+    }
500
+
501
+    /**
502
+     * A custom storage implementation can return an url for direct download of a give file.
503
+     *
504
+     * For now the returned array can hold the parameter url - in future more attributes might follow.
505
+     *
506
+     * @param string $path
507
+     * @return array|false
508
+     */
509
+    public function getDirectDownload($path) {
510
+        return [];
511
+    }
512
+
513
+    /**
514
+     * @inheritdoc
515
+     * @throws InvalidPathException
516
+     */
517
+    public function verifyPath($path, $fileName) {
518
+
519
+        // verify empty and dot files
520
+        $trimmed = trim($fileName);
521
+        if ($trimmed === '') {
522
+            throw new EmptyFileNameException();
523
+        }
524
+
525
+        if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
526
+            throw new InvalidDirectoryException();
527
+        }
528
+
529
+        if (!\OC::$server->getDatabaseConnection()->supports4ByteText()) {
530
+            // verify database - e.g. mysql only 3-byte chars
531
+            if (preg_match('%(?:
532 532
       \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
533 533
     | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
534 534
     | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
535 535
 )%xs', $fileName)) {
536
-				throw new InvalidCharacterInPathException();
537
-			}
538
-		}
539
-
540
-		// 255 characters is the limit on common file systems (ext/xfs)
541
-		// oc_filecache has a 250 char length limit for the filename
542
-		if (isset($fileName[250])) {
543
-			throw new FileNameTooLongException();
544
-		}
545
-
546
-		// NOTE: $path will remain unverified for now
547
-		$this->verifyPosixPath($fileName);
548
-	}
549
-
550
-	/**
551
-	 * @param string $fileName
552
-	 * @throws InvalidPathException
553
-	 */
554
-	protected function verifyPosixPath($fileName) {
555
-		$fileName = trim($fileName);
556
-		$this->scanForInvalidCharacters($fileName, "\\/");
557
-		$reservedNames = ['*'];
558
-		if (in_array($fileName, $reservedNames)) {
559
-			throw new ReservedWordException();
560
-		}
561
-	}
562
-
563
-	/**
564
-	 * @param string $fileName
565
-	 * @param string $invalidChars
566
-	 * @throws InvalidPathException
567
-	 */
568
-	private function scanForInvalidCharacters($fileName, $invalidChars) {
569
-		foreach (str_split($invalidChars) as $char) {
570
-			if (strpos($fileName, $char) !== false) {
571
-				throw new InvalidCharacterInPathException();
572
-			}
573
-		}
574
-
575
-		$sanitizedFileName = filter_var($fileName, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
576
-		if ($sanitizedFileName !== $fileName) {
577
-			throw new InvalidCharacterInPathException();
578
-		}
579
-	}
580
-
581
-	/**
582
-	 * @param array $options
583
-	 */
584
-	public function setMountOptions(array $options) {
585
-		$this->mountOptions = $options;
586
-	}
587
-
588
-	/**
589
-	 * @param string $name
590
-	 * @param mixed $default
591
-	 * @return mixed
592
-	 */
593
-	public function getMountOption($name, $default = null) {
594
-		return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default;
595
-	}
596
-
597
-	/**
598
-	 * @param IStorage $sourceStorage
599
-	 * @param string $sourceInternalPath
600
-	 * @param string $targetInternalPath
601
-	 * @param bool $preserveMtime
602
-	 * @return bool
603
-	 */
604
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
605
-		if ($sourceStorage === $this) {
606
-			return $this->copy($sourceInternalPath, $targetInternalPath);
607
-		}
608
-
609
-		if ($sourceStorage->is_dir($sourceInternalPath)) {
610
-			$dh = $sourceStorage->opendir($sourceInternalPath);
611
-			$result = $this->mkdir($targetInternalPath);
612
-			if (is_resource($dh)) {
613
-				while ($result and ($file = readdir($dh)) !== false) {
614
-					if (!Filesystem::isIgnoredDir($file)) {
615
-						$result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file);
616
-					}
617
-				}
618
-			}
619
-		} else {
620
-			$source = $sourceStorage->fopen($sourceInternalPath, 'r');
621
-			// TODO: call fopen in a way that we execute again all storage wrappers
622
-			// to avoid that we bypass storage wrappers which perform important actions
623
-			// for this operation. Same is true for all other operations which
624
-			// are not the same as the original one.Once this is fixed we also
625
-			// need to adjust the encryption wrapper.
626
-			$target = $this->fopen($targetInternalPath, 'w');
627
-			list(, $result) = \OC_Helper::streamCopy($source, $target);
628
-			if ($result and $preserveMtime) {
629
-				$this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath));
630
-			}
631
-			fclose($source);
632
-			fclose($target);
633
-
634
-			if (!$result) {
635
-				// delete partially written target file
636
-				$this->unlink($targetInternalPath);
637
-				// delete cache entry that was created by fopen
638
-				$this->getCache()->remove($targetInternalPath);
639
-			}
640
-		}
641
-		return (bool)$result;
642
-	}
643
-
644
-	/**
645
-	 * Check if a storage is the same as the current one, including wrapped storages
646
-	 *
647
-	 * @param IStorage $storage
648
-	 * @return bool
649
-	 */
650
-	private function isSameStorage(IStorage $storage): bool {
651
-		while ($storage->instanceOfStorage(Wrapper::class)) {
652
-			/**
653
-			 * @var Wrapper $sourceStorage
654
-			 */
655
-			$storage = $storage->getWrapperStorage();
656
-		}
657
-
658
-		return $storage === $this;
659
-	}
660
-
661
-	/**
662
-	 * @param IStorage $sourceStorage
663
-	 * @param string $sourceInternalPath
664
-	 * @param string $targetInternalPath
665
-	 * @return bool
666
-	 */
667
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
668
-		if ($this->isSameStorage($sourceStorage)) {
669
-			// resolve any jailed paths
670
-			while ($sourceStorage->instanceOfStorage(Jail::class)) {
671
-				/**
672
-				 * @var Jail $sourceStorage
673
-				 */
674
-				$sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
675
-				$sourceStorage = $sourceStorage->getUnjailedStorage();
676
-			}
677
-
678
-			return $this->rename($sourceInternalPath, $targetInternalPath);
679
-		}
680
-
681
-		if (!$sourceStorage->isDeletable($sourceInternalPath)) {
682
-			return false;
683
-		}
684
-
685
-		$result = $this->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, true);
686
-		if ($result) {
687
-			if ($sourceStorage->is_dir($sourceInternalPath)) {
688
-				$result &= $sourceStorage->rmdir($sourceInternalPath);
689
-			} else {
690
-				$result &= $sourceStorage->unlink($sourceInternalPath);
691
-			}
692
-		}
693
-		return $result;
694
-	}
695
-
696
-	/**
697
-	 * @inheritdoc
698
-	 */
699
-	public function getMetaData($path) {
700
-		$permissions = $this->getPermissions($path);
701
-		if (!$permissions & \OCP\Constants::PERMISSION_READ) {
702
-			//can't read, nothing we can do
703
-			return null;
704
-		}
705
-
706
-		$data = [];
707
-		$data['mimetype'] = $this->getMimeType($path);
708
-		$data['mtime'] = $this->filemtime($path);
709
-		if ($data['mtime'] === false) {
710
-			$data['mtime'] = time();
711
-		}
712
-		if ($data['mimetype'] == 'httpd/unix-directory') {
713
-			$data['size'] = -1; //unknown
714
-		} else {
715
-			$data['size'] = $this->filesize($path);
716
-		}
717
-		$data['etag'] = $this->getETag($path);
718
-		$data['storage_mtime'] = $data['mtime'];
719
-		$data['permissions'] = $permissions;
720
-
721
-		return $data;
722
-	}
723
-
724
-	/**
725
-	 * @param string $path
726
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
727
-	 * @param \OCP\Lock\ILockingProvider $provider
728
-	 * @throws \OCP\Lock\LockedException
729
-	 */
730
-	public function acquireLock($path, $type, ILockingProvider $provider) {
731
-		$logger = $this->getLockLogger();
732
-		if ($logger) {
733
-			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
734
-			$logger->info(
735
-				sprintf(
736
-					'acquire %s lock on "%s" on storage "%s"',
737
-					$typeString,
738
-					$path,
739
-					$this->getId()
740
-				),
741
-				[
742
-					'app' => 'locking',
743
-				]
744
-			);
745
-		}
746
-		try {
747
-			$provider->acquireLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
748
-		} catch (LockedException $e) {
749
-			if ($logger) {
750
-				$logger->logException($e, ['level' => ILogger::INFO]);
751
-			}
752
-			throw $e;
753
-		}
754
-	}
755
-
756
-	/**
757
-	 * @param string $path
758
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
759
-	 * @param \OCP\Lock\ILockingProvider $provider
760
-	 * @throws \OCP\Lock\LockedException
761
-	 */
762
-	public function releaseLock($path, $type, ILockingProvider $provider) {
763
-		$logger = $this->getLockLogger();
764
-		if ($logger) {
765
-			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
766
-			$logger->info(
767
-				sprintf(
768
-					'release %s lock on "%s" on storage "%s"',
769
-					$typeString,
770
-					$path,
771
-					$this->getId()
772
-				),
773
-				[
774
-					'app' => 'locking',
775
-				]
776
-			);
777
-		}
778
-		try {
779
-			$provider->releaseLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
780
-		} catch (LockedException $e) {
781
-			if ($logger) {
782
-				$logger->logException($e, ['level' => ILogger::INFO]);
783
-			}
784
-			throw $e;
785
-		}
786
-	}
787
-
788
-	/**
789
-	 * @param string $path
790
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
791
-	 * @param \OCP\Lock\ILockingProvider $provider
792
-	 * @throws \OCP\Lock\LockedException
793
-	 */
794
-	public function changeLock($path, $type, ILockingProvider $provider) {
795
-		$logger = $this->getLockLogger();
796
-		if ($logger) {
797
-			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
798
-			$logger->info(
799
-				sprintf(
800
-					'change lock on "%s" to %s on storage "%s"',
801
-					$path,
802
-					$typeString,
803
-					$this->getId()
804
-				),
805
-				[
806
-					'app' => 'locking',
807
-				]
808
-			);
809
-		}
810
-		try {
811
-			$provider->changeLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
812
-		} catch (LockedException $e) {
813
-			\OC::$server->getLogger()->logException($e, ['level' => ILogger::INFO]);
814
-			throw $e;
815
-		}
816
-	}
817
-
818
-	private function getLockLogger() {
819
-		if (is_null($this->shouldLogLocks)) {
820
-			$this->shouldLogLocks = \OC::$server->getConfig()->getSystemValue('filelocking.debug', false);
821
-			$this->logger = $this->shouldLogLocks ? \OC::$server->getLogger() : null;
822
-		}
823
-		return $this->logger;
824
-	}
825
-
826
-	/**
827
-	 * @return array [ available, last_checked ]
828
-	 */
829
-	public function getAvailability() {
830
-		return $this->getStorageCache()->getAvailability();
831
-	}
832
-
833
-	/**
834
-	 * @param bool $isAvailable
835
-	 */
836
-	public function setAvailability($isAvailable) {
837
-		$this->getStorageCache()->setAvailability($isAvailable);
838
-	}
839
-
840
-	/**
841
-	 * @return bool
842
-	 */
843
-	public function needsPartFile() {
844
-		return true;
845
-	}
846
-
847
-	/**
848
-	 * fallback implementation
849
-	 *
850
-	 * @param string $path
851
-	 * @param resource $stream
852
-	 * @param int $size
853
-	 * @return int
854
-	 */
855
-	public function writeStream(string $path, $stream, int $size = null): int {
856
-		$target = $this->fopen($path, 'w');
857
-		if (!$target) {
858
-			return 0;
859
-		}
860
-		list($count, $result) = \OC_Helper::streamCopy($stream, $target);
861
-		fclose($stream);
862
-		fclose($target);
863
-		return $count;
864
-	}
536
+                throw new InvalidCharacterInPathException();
537
+            }
538
+        }
539
+
540
+        // 255 characters is the limit on common file systems (ext/xfs)
541
+        // oc_filecache has a 250 char length limit for the filename
542
+        if (isset($fileName[250])) {
543
+            throw new FileNameTooLongException();
544
+        }
545
+
546
+        // NOTE: $path will remain unverified for now
547
+        $this->verifyPosixPath($fileName);
548
+    }
549
+
550
+    /**
551
+     * @param string $fileName
552
+     * @throws InvalidPathException
553
+     */
554
+    protected function verifyPosixPath($fileName) {
555
+        $fileName = trim($fileName);
556
+        $this->scanForInvalidCharacters($fileName, "\\/");
557
+        $reservedNames = ['*'];
558
+        if (in_array($fileName, $reservedNames)) {
559
+            throw new ReservedWordException();
560
+        }
561
+    }
562
+
563
+    /**
564
+     * @param string $fileName
565
+     * @param string $invalidChars
566
+     * @throws InvalidPathException
567
+     */
568
+    private function scanForInvalidCharacters($fileName, $invalidChars) {
569
+        foreach (str_split($invalidChars) as $char) {
570
+            if (strpos($fileName, $char) !== false) {
571
+                throw new InvalidCharacterInPathException();
572
+            }
573
+        }
574
+
575
+        $sanitizedFileName = filter_var($fileName, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
576
+        if ($sanitizedFileName !== $fileName) {
577
+            throw new InvalidCharacterInPathException();
578
+        }
579
+    }
580
+
581
+    /**
582
+     * @param array $options
583
+     */
584
+    public function setMountOptions(array $options) {
585
+        $this->mountOptions = $options;
586
+    }
587
+
588
+    /**
589
+     * @param string $name
590
+     * @param mixed $default
591
+     * @return mixed
592
+     */
593
+    public function getMountOption($name, $default = null) {
594
+        return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default;
595
+    }
596
+
597
+    /**
598
+     * @param IStorage $sourceStorage
599
+     * @param string $sourceInternalPath
600
+     * @param string $targetInternalPath
601
+     * @param bool $preserveMtime
602
+     * @return bool
603
+     */
604
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
605
+        if ($sourceStorage === $this) {
606
+            return $this->copy($sourceInternalPath, $targetInternalPath);
607
+        }
608
+
609
+        if ($sourceStorage->is_dir($sourceInternalPath)) {
610
+            $dh = $sourceStorage->opendir($sourceInternalPath);
611
+            $result = $this->mkdir($targetInternalPath);
612
+            if (is_resource($dh)) {
613
+                while ($result and ($file = readdir($dh)) !== false) {
614
+                    if (!Filesystem::isIgnoredDir($file)) {
615
+                        $result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file);
616
+                    }
617
+                }
618
+            }
619
+        } else {
620
+            $source = $sourceStorage->fopen($sourceInternalPath, 'r');
621
+            // TODO: call fopen in a way that we execute again all storage wrappers
622
+            // to avoid that we bypass storage wrappers which perform important actions
623
+            // for this operation. Same is true for all other operations which
624
+            // are not the same as the original one.Once this is fixed we also
625
+            // need to adjust the encryption wrapper.
626
+            $target = $this->fopen($targetInternalPath, 'w');
627
+            list(, $result) = \OC_Helper::streamCopy($source, $target);
628
+            if ($result and $preserveMtime) {
629
+                $this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath));
630
+            }
631
+            fclose($source);
632
+            fclose($target);
633
+
634
+            if (!$result) {
635
+                // delete partially written target file
636
+                $this->unlink($targetInternalPath);
637
+                // delete cache entry that was created by fopen
638
+                $this->getCache()->remove($targetInternalPath);
639
+            }
640
+        }
641
+        return (bool)$result;
642
+    }
643
+
644
+    /**
645
+     * Check if a storage is the same as the current one, including wrapped storages
646
+     *
647
+     * @param IStorage $storage
648
+     * @return bool
649
+     */
650
+    private function isSameStorage(IStorage $storage): bool {
651
+        while ($storage->instanceOfStorage(Wrapper::class)) {
652
+            /**
653
+             * @var Wrapper $sourceStorage
654
+             */
655
+            $storage = $storage->getWrapperStorage();
656
+        }
657
+
658
+        return $storage === $this;
659
+    }
660
+
661
+    /**
662
+     * @param IStorage $sourceStorage
663
+     * @param string $sourceInternalPath
664
+     * @param string $targetInternalPath
665
+     * @return bool
666
+     */
667
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
668
+        if ($this->isSameStorage($sourceStorage)) {
669
+            // resolve any jailed paths
670
+            while ($sourceStorage->instanceOfStorage(Jail::class)) {
671
+                /**
672
+                 * @var Jail $sourceStorage
673
+                 */
674
+                $sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
675
+                $sourceStorage = $sourceStorage->getUnjailedStorage();
676
+            }
677
+
678
+            return $this->rename($sourceInternalPath, $targetInternalPath);
679
+        }
680
+
681
+        if (!$sourceStorage->isDeletable($sourceInternalPath)) {
682
+            return false;
683
+        }
684
+
685
+        $result = $this->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, true);
686
+        if ($result) {
687
+            if ($sourceStorage->is_dir($sourceInternalPath)) {
688
+                $result &= $sourceStorage->rmdir($sourceInternalPath);
689
+            } else {
690
+                $result &= $sourceStorage->unlink($sourceInternalPath);
691
+            }
692
+        }
693
+        return $result;
694
+    }
695
+
696
+    /**
697
+     * @inheritdoc
698
+     */
699
+    public function getMetaData($path) {
700
+        $permissions = $this->getPermissions($path);
701
+        if (!$permissions & \OCP\Constants::PERMISSION_READ) {
702
+            //can't read, nothing we can do
703
+            return null;
704
+        }
705
+
706
+        $data = [];
707
+        $data['mimetype'] = $this->getMimeType($path);
708
+        $data['mtime'] = $this->filemtime($path);
709
+        if ($data['mtime'] === false) {
710
+            $data['mtime'] = time();
711
+        }
712
+        if ($data['mimetype'] == 'httpd/unix-directory') {
713
+            $data['size'] = -1; //unknown
714
+        } else {
715
+            $data['size'] = $this->filesize($path);
716
+        }
717
+        $data['etag'] = $this->getETag($path);
718
+        $data['storage_mtime'] = $data['mtime'];
719
+        $data['permissions'] = $permissions;
720
+
721
+        return $data;
722
+    }
723
+
724
+    /**
725
+     * @param string $path
726
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
727
+     * @param \OCP\Lock\ILockingProvider $provider
728
+     * @throws \OCP\Lock\LockedException
729
+     */
730
+    public function acquireLock($path, $type, ILockingProvider $provider) {
731
+        $logger = $this->getLockLogger();
732
+        if ($logger) {
733
+            $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
734
+            $logger->info(
735
+                sprintf(
736
+                    'acquire %s lock on "%s" on storage "%s"',
737
+                    $typeString,
738
+                    $path,
739
+                    $this->getId()
740
+                ),
741
+                [
742
+                    'app' => 'locking',
743
+                ]
744
+            );
745
+        }
746
+        try {
747
+            $provider->acquireLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
748
+        } catch (LockedException $e) {
749
+            if ($logger) {
750
+                $logger->logException($e, ['level' => ILogger::INFO]);
751
+            }
752
+            throw $e;
753
+        }
754
+    }
755
+
756
+    /**
757
+     * @param string $path
758
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
759
+     * @param \OCP\Lock\ILockingProvider $provider
760
+     * @throws \OCP\Lock\LockedException
761
+     */
762
+    public function releaseLock($path, $type, ILockingProvider $provider) {
763
+        $logger = $this->getLockLogger();
764
+        if ($logger) {
765
+            $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
766
+            $logger->info(
767
+                sprintf(
768
+                    'release %s lock on "%s" on storage "%s"',
769
+                    $typeString,
770
+                    $path,
771
+                    $this->getId()
772
+                ),
773
+                [
774
+                    'app' => 'locking',
775
+                ]
776
+            );
777
+        }
778
+        try {
779
+            $provider->releaseLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
780
+        } catch (LockedException $e) {
781
+            if ($logger) {
782
+                $logger->logException($e, ['level' => ILogger::INFO]);
783
+            }
784
+            throw $e;
785
+        }
786
+    }
787
+
788
+    /**
789
+     * @param string $path
790
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
791
+     * @param \OCP\Lock\ILockingProvider $provider
792
+     * @throws \OCP\Lock\LockedException
793
+     */
794
+    public function changeLock($path, $type, ILockingProvider $provider) {
795
+        $logger = $this->getLockLogger();
796
+        if ($logger) {
797
+            $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
798
+            $logger->info(
799
+                sprintf(
800
+                    'change lock on "%s" to %s on storage "%s"',
801
+                    $path,
802
+                    $typeString,
803
+                    $this->getId()
804
+                ),
805
+                [
806
+                    'app' => 'locking',
807
+                ]
808
+            );
809
+        }
810
+        try {
811
+            $provider->changeLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
812
+        } catch (LockedException $e) {
813
+            \OC::$server->getLogger()->logException($e, ['level' => ILogger::INFO]);
814
+            throw $e;
815
+        }
816
+    }
817
+
818
+    private function getLockLogger() {
819
+        if (is_null($this->shouldLogLocks)) {
820
+            $this->shouldLogLocks = \OC::$server->getConfig()->getSystemValue('filelocking.debug', false);
821
+            $this->logger = $this->shouldLogLocks ? \OC::$server->getLogger() : null;
822
+        }
823
+        return $this->logger;
824
+    }
825
+
826
+    /**
827
+     * @return array [ available, last_checked ]
828
+     */
829
+    public function getAvailability() {
830
+        return $this->getStorageCache()->getAvailability();
831
+    }
832
+
833
+    /**
834
+     * @param bool $isAvailable
835
+     */
836
+    public function setAvailability($isAvailable) {
837
+        $this->getStorageCache()->setAvailability($isAvailable);
838
+    }
839
+
840
+    /**
841
+     * @return bool
842
+     */
843
+    public function needsPartFile() {
844
+        return true;
845
+    }
846
+
847
+    /**
848
+     * fallback implementation
849
+     *
850
+     * @param string $path
851
+     * @param resource $stream
852
+     * @param int $size
853
+     * @return int
854
+     */
855
+    public function writeStream(string $path, $stream, int $size = null): int {
856
+        $target = $this->fopen($path, 'w');
857
+        if (!$target) {
858
+            return 0;
859
+        }
860
+        list($count, $result) = \OC_Helper::streamCopy($stream, $target);
861
+        fclose($stream);
862
+        fclose($target);
863
+        return $count;
864
+    }
865 865
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/Wrapper/Wrapper.php 1 patch
Indentation   +600 added lines, -600 removed lines patch added patch discarded remove patch
@@ -36,604 +36,604 @@
 block discarded – undo
36 36
 use OCP\Lock\ILockingProvider;
37 37
 
38 38
 class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage, IWriteStreamStorage {
39
-	/**
40
-	 * @var \OC\Files\Storage\Storage $storage
41
-	 */
42
-	protected $storage;
43
-
44
-	public $cache;
45
-	public $scanner;
46
-	public $watcher;
47
-	public $propagator;
48
-	public $updater;
49
-
50
-	/**
51
-	 * @param array $parameters
52
-	 */
53
-	public function __construct($parameters) {
54
-		$this->storage = $parameters['storage'];
55
-	}
56
-
57
-	/**
58
-	 * @return \OC\Files\Storage\Storage
59
-	 */
60
-	public function getWrapperStorage() {
61
-		return $this->storage;
62
-	}
63
-
64
-	/**
65
-	 * Get the identifier for the storage,
66
-	 * the returned id should be the same for every storage object that is created with the same parameters
67
-	 * and two storage objects with the same id should refer to two storages that display the same files.
68
-	 *
69
-	 * @return string
70
-	 */
71
-	public function getId() {
72
-		return $this->getWrapperStorage()->getId();
73
-	}
74
-
75
-	/**
76
-	 * see http://php.net/manual/en/function.mkdir.php
77
-	 *
78
-	 * @param string $path
79
-	 * @return bool
80
-	 */
81
-	public function mkdir($path) {
82
-		return $this->getWrapperStorage()->mkdir($path);
83
-	}
84
-
85
-	/**
86
-	 * see http://php.net/manual/en/function.rmdir.php
87
-	 *
88
-	 * @param string $path
89
-	 * @return bool
90
-	 */
91
-	public function rmdir($path) {
92
-		return $this->getWrapperStorage()->rmdir($path);
93
-	}
94
-
95
-	/**
96
-	 * see http://php.net/manual/en/function.opendir.php
97
-	 *
98
-	 * @param string $path
99
-	 * @return resource
100
-	 */
101
-	public function opendir($path) {
102
-		return $this->getWrapperStorage()->opendir($path);
103
-	}
104
-
105
-	/**
106
-	 * see http://php.net/manual/en/function.is_dir.php
107
-	 *
108
-	 * @param string $path
109
-	 * @return bool
110
-	 */
111
-	public function is_dir($path) {
112
-		return $this->getWrapperStorage()->is_dir($path);
113
-	}
114
-
115
-	/**
116
-	 * see http://php.net/manual/en/function.is_file.php
117
-	 *
118
-	 * @param string $path
119
-	 * @return bool
120
-	 */
121
-	public function is_file($path) {
122
-		return $this->getWrapperStorage()->is_file($path);
123
-	}
124
-
125
-	/**
126
-	 * see http://php.net/manual/en/function.stat.php
127
-	 * only the following keys are required in the result: size and mtime
128
-	 *
129
-	 * @param string $path
130
-	 * @return array
131
-	 */
132
-	public function stat($path) {
133
-		return $this->getWrapperStorage()->stat($path);
134
-	}
135
-
136
-	/**
137
-	 * see http://php.net/manual/en/function.filetype.php
138
-	 *
139
-	 * @param string $path
140
-	 * @return bool
141
-	 */
142
-	public function filetype($path) {
143
-		return $this->getWrapperStorage()->filetype($path);
144
-	}
145
-
146
-	/**
147
-	 * see http://php.net/manual/en/function.filesize.php
148
-	 * The result for filesize when called on a folder is required to be 0
149
-	 *
150
-	 * @param string $path
151
-	 * @return int
152
-	 */
153
-	public function filesize($path) {
154
-		return $this->getWrapperStorage()->filesize($path);
155
-	}
156
-
157
-	/**
158
-	 * check if a file can be created in $path
159
-	 *
160
-	 * @param string $path
161
-	 * @return bool
162
-	 */
163
-	public function isCreatable($path) {
164
-		return $this->getWrapperStorage()->isCreatable($path);
165
-	}
166
-
167
-	/**
168
-	 * check if a file can be read
169
-	 *
170
-	 * @param string $path
171
-	 * @return bool
172
-	 */
173
-	public function isReadable($path) {
174
-		return $this->getWrapperStorage()->isReadable($path);
175
-	}
176
-
177
-	/**
178
-	 * check if a file can be written to
179
-	 *
180
-	 * @param string $path
181
-	 * @return bool
182
-	 */
183
-	public function isUpdatable($path) {
184
-		return $this->getWrapperStorage()->isUpdatable($path);
185
-	}
186
-
187
-	/**
188
-	 * check if a file can be deleted
189
-	 *
190
-	 * @param string $path
191
-	 * @return bool
192
-	 */
193
-	public function isDeletable($path) {
194
-		return $this->getWrapperStorage()->isDeletable($path);
195
-	}
196
-
197
-	/**
198
-	 * check if a file can be shared
199
-	 *
200
-	 * @param string $path
201
-	 * @return bool
202
-	 */
203
-	public function isSharable($path) {
204
-		return $this->getWrapperStorage()->isSharable($path);
205
-	}
206
-
207
-	/**
208
-	 * get the full permissions of a path.
209
-	 * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
210
-	 *
211
-	 * @param string $path
212
-	 * @return int
213
-	 */
214
-	public function getPermissions($path) {
215
-		return $this->getWrapperStorage()->getPermissions($path);
216
-	}
217
-
218
-	/**
219
-	 * see http://php.net/manual/en/function.file_exists.php
220
-	 *
221
-	 * @param string $path
222
-	 * @return bool
223
-	 */
224
-	public function file_exists($path) {
225
-		return $this->getWrapperStorage()->file_exists($path);
226
-	}
227
-
228
-	/**
229
-	 * see http://php.net/manual/en/function.filemtime.php
230
-	 *
231
-	 * @param string $path
232
-	 * @return int
233
-	 */
234
-	public function filemtime($path) {
235
-		return $this->getWrapperStorage()->filemtime($path);
236
-	}
237
-
238
-	/**
239
-	 * see http://php.net/manual/en/function.file_get_contents.php
240
-	 *
241
-	 * @param string $path
242
-	 * @return string
243
-	 */
244
-	public function file_get_contents($path) {
245
-		return $this->getWrapperStorage()->file_get_contents($path);
246
-	}
247
-
248
-	/**
249
-	 * see http://php.net/manual/en/function.file_put_contents.php
250
-	 *
251
-	 * @param string $path
252
-	 * @param string $data
253
-	 * @return bool
254
-	 */
255
-	public function file_put_contents($path, $data) {
256
-		return $this->getWrapperStorage()->file_put_contents($path, $data);
257
-	}
258
-
259
-	/**
260
-	 * see http://php.net/manual/en/function.unlink.php
261
-	 *
262
-	 * @param string $path
263
-	 * @return bool
264
-	 */
265
-	public function unlink($path) {
266
-		return $this->getWrapperStorage()->unlink($path);
267
-	}
268
-
269
-	/**
270
-	 * see http://php.net/manual/en/function.rename.php
271
-	 *
272
-	 * @param string $path1
273
-	 * @param string $path2
274
-	 * @return bool
275
-	 */
276
-	public function rename($path1, $path2) {
277
-		return $this->getWrapperStorage()->rename($path1, $path2);
278
-	}
279
-
280
-	/**
281
-	 * see http://php.net/manual/en/function.copy.php
282
-	 *
283
-	 * @param string $path1
284
-	 * @param string $path2
285
-	 * @return bool
286
-	 */
287
-	public function copy($path1, $path2) {
288
-		return $this->getWrapperStorage()->copy($path1, $path2);
289
-	}
290
-
291
-	/**
292
-	 * see http://php.net/manual/en/function.fopen.php
293
-	 *
294
-	 * @param string $path
295
-	 * @param string $mode
296
-	 * @return resource
297
-	 */
298
-	public function fopen($path, $mode) {
299
-		return $this->getWrapperStorage()->fopen($path, $mode);
300
-	}
301
-
302
-	/**
303
-	 * get the mimetype for a file or folder
304
-	 * The mimetype for a folder is required to be "httpd/unix-directory"
305
-	 *
306
-	 * @param string $path
307
-	 * @return string
308
-	 */
309
-	public function getMimeType($path) {
310
-		return $this->getWrapperStorage()->getMimeType($path);
311
-	}
312
-
313
-	/**
314
-	 * see http://php.net/manual/en/function.hash.php
315
-	 *
316
-	 * @param string $type
317
-	 * @param string $path
318
-	 * @param bool $raw
319
-	 * @return string
320
-	 */
321
-	public function hash($type, $path, $raw = false) {
322
-		return $this->getWrapperStorage()->hash($type, $path, $raw);
323
-	}
324
-
325
-	/**
326
-	 * see http://php.net/manual/en/function.free_space.php
327
-	 *
328
-	 * @param string $path
329
-	 * @return int
330
-	 */
331
-	public function free_space($path) {
332
-		return $this->getWrapperStorage()->free_space($path);
333
-	}
334
-
335
-	/**
336
-	 * search for occurrences of $query in file names
337
-	 *
338
-	 * @param string $query
339
-	 * @return array
340
-	 */
341
-	public function search($query) {
342
-		return $this->getWrapperStorage()->search($query);
343
-	}
344
-
345
-	/**
346
-	 * see http://php.net/manual/en/function.touch.php
347
-	 * If the backend does not support the operation, false should be returned
348
-	 *
349
-	 * @param string $path
350
-	 * @param int $mtime
351
-	 * @return bool
352
-	 */
353
-	public function touch($path, $mtime = null) {
354
-		return $this->getWrapperStorage()->touch($path, $mtime);
355
-	}
356
-
357
-	/**
358
-	 * get the path to a local version of the file.
359
-	 * The local version of the file can be temporary and doesn't have to be persistent across requests
360
-	 *
361
-	 * @param string $path
362
-	 * @return string
363
-	 */
364
-	public function getLocalFile($path) {
365
-		return $this->getWrapperStorage()->getLocalFile($path);
366
-	}
367
-
368
-	/**
369
-	 * check if a file or folder has been updated since $time
370
-	 *
371
-	 * @param string $path
372
-	 * @param int $time
373
-	 * @return bool
374
-	 *
375
-	 * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
376
-	 * returning true for other changes in the folder is optional
377
-	 */
378
-	public function hasUpdated($path, $time) {
379
-		return $this->getWrapperStorage()->hasUpdated($path, $time);
380
-	}
381
-
382
-	/**
383
-	 * get a cache instance for the storage
384
-	 *
385
-	 * @param string $path
386
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
387
-	 * @return \OC\Files\Cache\Cache
388
-	 */
389
-	public function getCache($path = '', $storage = null) {
390
-		if (!$storage) {
391
-			$storage = $this;
392
-		}
393
-		return $this->getWrapperStorage()->getCache($path, $storage);
394
-	}
395
-
396
-	/**
397
-	 * get a scanner instance for the storage
398
-	 *
399
-	 * @param string $path
400
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
401
-	 * @return \OC\Files\Cache\Scanner
402
-	 */
403
-	public function getScanner($path = '', $storage = null) {
404
-		if (!$storage) {
405
-			$storage = $this;
406
-		}
407
-		return $this->getWrapperStorage()->getScanner($path, $storage);
408
-	}
409
-
410
-
411
-	/**
412
-	 * get the user id of the owner of a file or folder
413
-	 *
414
-	 * @param string $path
415
-	 * @return string
416
-	 */
417
-	public function getOwner($path) {
418
-		return $this->getWrapperStorage()->getOwner($path);
419
-	}
420
-
421
-	/**
422
-	 * get a watcher instance for the cache
423
-	 *
424
-	 * @param string $path
425
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
426
-	 * @return \OC\Files\Cache\Watcher
427
-	 */
428
-	public function getWatcher($path = '', $storage = null) {
429
-		if (!$storage) {
430
-			$storage = $this;
431
-		}
432
-		return $this->getWrapperStorage()->getWatcher($path, $storage);
433
-	}
434
-
435
-	public function getPropagator($storage = null) {
436
-		if (!$storage) {
437
-			$storage = $this;
438
-		}
439
-		return $this->getWrapperStorage()->getPropagator($storage);
440
-	}
441
-
442
-	public function getUpdater($storage = null) {
443
-		if (!$storage) {
444
-			$storage = $this;
445
-		}
446
-		return $this->getWrapperStorage()->getUpdater($storage);
447
-	}
448
-
449
-	/**
450
-	 * @return \OC\Files\Cache\Storage
451
-	 */
452
-	public function getStorageCache() {
453
-		return $this->getWrapperStorage()->getStorageCache();
454
-	}
455
-
456
-	/**
457
-	 * get the ETag for a file or folder
458
-	 *
459
-	 * @param string $path
460
-	 * @return string
461
-	 */
462
-	public function getETag($path) {
463
-		return $this->getWrapperStorage()->getETag($path);
464
-	}
465
-
466
-	/**
467
-	 * Returns true
468
-	 *
469
-	 * @return true
470
-	 */
471
-	public function test() {
472
-		return $this->getWrapperStorage()->test();
473
-	}
474
-
475
-	/**
476
-	 * Returns the wrapped storage's value for isLocal()
477
-	 *
478
-	 * @return bool wrapped storage's isLocal() value
479
-	 */
480
-	public function isLocal() {
481
-		return $this->getWrapperStorage()->isLocal();
482
-	}
483
-
484
-	/**
485
-	 * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
486
-	 *
487
-	 * @param string $class
488
-	 * @return bool
489
-	 */
490
-	public function instanceOfStorage($class) {
491
-		if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
492
-			// FIXME Temporary fix to keep existing checks working
493
-			$class = '\OCA\Files_Sharing\SharedStorage';
494
-		}
495
-		return is_a($this, $class) or $this->getWrapperStorage()->instanceOfStorage($class);
496
-	}
497
-
498
-	/**
499
-	 * Pass any methods custom to specific storage implementations to the wrapped storage
500
-	 *
501
-	 * @param string $method
502
-	 * @param array $args
503
-	 * @return mixed
504
-	 */
505
-	public function __call($method, $args) {
506
-		return call_user_func_array([$this->getWrapperStorage(), $method], $args);
507
-	}
508
-
509
-	/**
510
-	 * A custom storage implementation can return an url for direct download of a give file.
511
-	 *
512
-	 * For now the returned array can hold the parameter url - in future more attributes might follow.
513
-	 *
514
-	 * @param string $path
515
-	 * @return array
516
-	 */
517
-	public function getDirectDownload($path) {
518
-		return $this->getWrapperStorage()->getDirectDownload($path);
519
-	}
520
-
521
-	/**
522
-	 * Get availability of the storage
523
-	 *
524
-	 * @return array [ available, last_checked ]
525
-	 */
526
-	public function getAvailability() {
527
-		return $this->getWrapperStorage()->getAvailability();
528
-	}
529
-
530
-	/**
531
-	 * Set availability of the storage
532
-	 *
533
-	 * @param bool $isAvailable
534
-	 */
535
-	public function setAvailability($isAvailable) {
536
-		$this->getWrapperStorage()->setAvailability($isAvailable);
537
-	}
538
-
539
-	/**
540
-	 * @param string $path the path of the target folder
541
-	 * @param string $fileName the name of the file itself
542
-	 * @return void
543
-	 * @throws InvalidPathException
544
-	 */
545
-	public function verifyPath($path, $fileName) {
546
-		$this->getWrapperStorage()->verifyPath($path, $fileName);
547
-	}
548
-
549
-	/**
550
-	 * @param IStorage $sourceStorage
551
-	 * @param string $sourceInternalPath
552
-	 * @param string $targetInternalPath
553
-	 * @return bool
554
-	 */
555
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
556
-		if ($sourceStorage === $this) {
557
-			return $this->copy($sourceInternalPath, $targetInternalPath);
558
-		}
559
-
560
-		return $this->getWrapperStorage()->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
561
-	}
562
-
563
-	/**
564
-	 * @param IStorage $sourceStorage
565
-	 * @param string $sourceInternalPath
566
-	 * @param string $targetInternalPath
567
-	 * @return bool
568
-	 */
569
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
570
-		if ($sourceStorage === $this) {
571
-			return $this->rename($sourceInternalPath, $targetInternalPath);
572
-		}
573
-
574
-		return $this->getWrapperStorage()->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
575
-	}
576
-
577
-	/**
578
-	 * @param string $path
579
-	 * @return array
580
-	 */
581
-	public function getMetaData($path) {
582
-		return $this->getWrapperStorage()->getMetaData($path);
583
-	}
584
-
585
-	/**
586
-	 * @param string $path
587
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
588
-	 * @param \OCP\Lock\ILockingProvider $provider
589
-	 * @throws \OCP\Lock\LockedException
590
-	 */
591
-	public function acquireLock($path, $type, ILockingProvider $provider) {
592
-		if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
593
-			$this->getWrapperStorage()->acquireLock($path, $type, $provider);
594
-		}
595
-	}
596
-
597
-	/**
598
-	 * @param string $path
599
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
600
-	 * @param \OCP\Lock\ILockingProvider $provider
601
-	 */
602
-	public function releaseLock($path, $type, ILockingProvider $provider) {
603
-		if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
604
-			$this->getWrapperStorage()->releaseLock($path, $type, $provider);
605
-		}
606
-	}
607
-
608
-	/**
609
-	 * @param string $path
610
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
611
-	 * @param \OCP\Lock\ILockingProvider $provider
612
-	 */
613
-	public function changeLock($path, $type, ILockingProvider $provider) {
614
-		if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
615
-			$this->getWrapperStorage()->changeLock($path, $type, $provider);
616
-		}
617
-	}
618
-
619
-	/**
620
-	 * @return bool
621
-	 */
622
-	public function needsPartFile() {
623
-		return $this->getWrapperStorage()->needsPartFile();
624
-	}
625
-
626
-	public function writeStream(string $path, $stream, int $size = null): int {
627
-		$storage = $this->getWrapperStorage();
628
-		if ($storage->instanceOfStorage(IWriteStreamStorage::class)) {
629
-			/** @var IWriteStreamStorage $storage */
630
-			return $storage->writeStream($path, $stream, $size);
631
-		} else {
632
-			$target = $this->fopen($path, 'w');
633
-			list($count, $result) = \OC_Helper::streamCopy($stream, $target);
634
-			fclose($stream);
635
-			fclose($target);
636
-			return $count;
637
-		}
638
-	}
39
+    /**
40
+     * @var \OC\Files\Storage\Storage $storage
41
+     */
42
+    protected $storage;
43
+
44
+    public $cache;
45
+    public $scanner;
46
+    public $watcher;
47
+    public $propagator;
48
+    public $updater;
49
+
50
+    /**
51
+     * @param array $parameters
52
+     */
53
+    public function __construct($parameters) {
54
+        $this->storage = $parameters['storage'];
55
+    }
56
+
57
+    /**
58
+     * @return \OC\Files\Storage\Storage
59
+     */
60
+    public function getWrapperStorage() {
61
+        return $this->storage;
62
+    }
63
+
64
+    /**
65
+     * Get the identifier for the storage,
66
+     * the returned id should be the same for every storage object that is created with the same parameters
67
+     * and two storage objects with the same id should refer to two storages that display the same files.
68
+     *
69
+     * @return string
70
+     */
71
+    public function getId() {
72
+        return $this->getWrapperStorage()->getId();
73
+    }
74
+
75
+    /**
76
+     * see http://php.net/manual/en/function.mkdir.php
77
+     *
78
+     * @param string $path
79
+     * @return bool
80
+     */
81
+    public function mkdir($path) {
82
+        return $this->getWrapperStorage()->mkdir($path);
83
+    }
84
+
85
+    /**
86
+     * see http://php.net/manual/en/function.rmdir.php
87
+     *
88
+     * @param string $path
89
+     * @return bool
90
+     */
91
+    public function rmdir($path) {
92
+        return $this->getWrapperStorage()->rmdir($path);
93
+    }
94
+
95
+    /**
96
+     * see http://php.net/manual/en/function.opendir.php
97
+     *
98
+     * @param string $path
99
+     * @return resource
100
+     */
101
+    public function opendir($path) {
102
+        return $this->getWrapperStorage()->opendir($path);
103
+    }
104
+
105
+    /**
106
+     * see http://php.net/manual/en/function.is_dir.php
107
+     *
108
+     * @param string $path
109
+     * @return bool
110
+     */
111
+    public function is_dir($path) {
112
+        return $this->getWrapperStorage()->is_dir($path);
113
+    }
114
+
115
+    /**
116
+     * see http://php.net/manual/en/function.is_file.php
117
+     *
118
+     * @param string $path
119
+     * @return bool
120
+     */
121
+    public function is_file($path) {
122
+        return $this->getWrapperStorage()->is_file($path);
123
+    }
124
+
125
+    /**
126
+     * see http://php.net/manual/en/function.stat.php
127
+     * only the following keys are required in the result: size and mtime
128
+     *
129
+     * @param string $path
130
+     * @return array
131
+     */
132
+    public function stat($path) {
133
+        return $this->getWrapperStorage()->stat($path);
134
+    }
135
+
136
+    /**
137
+     * see http://php.net/manual/en/function.filetype.php
138
+     *
139
+     * @param string $path
140
+     * @return bool
141
+     */
142
+    public function filetype($path) {
143
+        return $this->getWrapperStorage()->filetype($path);
144
+    }
145
+
146
+    /**
147
+     * see http://php.net/manual/en/function.filesize.php
148
+     * The result for filesize when called on a folder is required to be 0
149
+     *
150
+     * @param string $path
151
+     * @return int
152
+     */
153
+    public function filesize($path) {
154
+        return $this->getWrapperStorage()->filesize($path);
155
+    }
156
+
157
+    /**
158
+     * check if a file can be created in $path
159
+     *
160
+     * @param string $path
161
+     * @return bool
162
+     */
163
+    public function isCreatable($path) {
164
+        return $this->getWrapperStorage()->isCreatable($path);
165
+    }
166
+
167
+    /**
168
+     * check if a file can be read
169
+     *
170
+     * @param string $path
171
+     * @return bool
172
+     */
173
+    public function isReadable($path) {
174
+        return $this->getWrapperStorage()->isReadable($path);
175
+    }
176
+
177
+    /**
178
+     * check if a file can be written to
179
+     *
180
+     * @param string $path
181
+     * @return bool
182
+     */
183
+    public function isUpdatable($path) {
184
+        return $this->getWrapperStorage()->isUpdatable($path);
185
+    }
186
+
187
+    /**
188
+     * check if a file can be deleted
189
+     *
190
+     * @param string $path
191
+     * @return bool
192
+     */
193
+    public function isDeletable($path) {
194
+        return $this->getWrapperStorage()->isDeletable($path);
195
+    }
196
+
197
+    /**
198
+     * check if a file can be shared
199
+     *
200
+     * @param string $path
201
+     * @return bool
202
+     */
203
+    public function isSharable($path) {
204
+        return $this->getWrapperStorage()->isSharable($path);
205
+    }
206
+
207
+    /**
208
+     * get the full permissions of a path.
209
+     * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
210
+     *
211
+     * @param string $path
212
+     * @return int
213
+     */
214
+    public function getPermissions($path) {
215
+        return $this->getWrapperStorage()->getPermissions($path);
216
+    }
217
+
218
+    /**
219
+     * see http://php.net/manual/en/function.file_exists.php
220
+     *
221
+     * @param string $path
222
+     * @return bool
223
+     */
224
+    public function file_exists($path) {
225
+        return $this->getWrapperStorage()->file_exists($path);
226
+    }
227
+
228
+    /**
229
+     * see http://php.net/manual/en/function.filemtime.php
230
+     *
231
+     * @param string $path
232
+     * @return int
233
+     */
234
+    public function filemtime($path) {
235
+        return $this->getWrapperStorage()->filemtime($path);
236
+    }
237
+
238
+    /**
239
+     * see http://php.net/manual/en/function.file_get_contents.php
240
+     *
241
+     * @param string $path
242
+     * @return string
243
+     */
244
+    public function file_get_contents($path) {
245
+        return $this->getWrapperStorage()->file_get_contents($path);
246
+    }
247
+
248
+    /**
249
+     * see http://php.net/manual/en/function.file_put_contents.php
250
+     *
251
+     * @param string $path
252
+     * @param string $data
253
+     * @return bool
254
+     */
255
+    public function file_put_contents($path, $data) {
256
+        return $this->getWrapperStorage()->file_put_contents($path, $data);
257
+    }
258
+
259
+    /**
260
+     * see http://php.net/manual/en/function.unlink.php
261
+     *
262
+     * @param string $path
263
+     * @return bool
264
+     */
265
+    public function unlink($path) {
266
+        return $this->getWrapperStorage()->unlink($path);
267
+    }
268
+
269
+    /**
270
+     * see http://php.net/manual/en/function.rename.php
271
+     *
272
+     * @param string $path1
273
+     * @param string $path2
274
+     * @return bool
275
+     */
276
+    public function rename($path1, $path2) {
277
+        return $this->getWrapperStorage()->rename($path1, $path2);
278
+    }
279
+
280
+    /**
281
+     * see http://php.net/manual/en/function.copy.php
282
+     *
283
+     * @param string $path1
284
+     * @param string $path2
285
+     * @return bool
286
+     */
287
+    public function copy($path1, $path2) {
288
+        return $this->getWrapperStorage()->copy($path1, $path2);
289
+    }
290
+
291
+    /**
292
+     * see http://php.net/manual/en/function.fopen.php
293
+     *
294
+     * @param string $path
295
+     * @param string $mode
296
+     * @return resource
297
+     */
298
+    public function fopen($path, $mode) {
299
+        return $this->getWrapperStorage()->fopen($path, $mode);
300
+    }
301
+
302
+    /**
303
+     * get the mimetype for a file or folder
304
+     * The mimetype for a folder is required to be "httpd/unix-directory"
305
+     *
306
+     * @param string $path
307
+     * @return string
308
+     */
309
+    public function getMimeType($path) {
310
+        return $this->getWrapperStorage()->getMimeType($path);
311
+    }
312
+
313
+    /**
314
+     * see http://php.net/manual/en/function.hash.php
315
+     *
316
+     * @param string $type
317
+     * @param string $path
318
+     * @param bool $raw
319
+     * @return string
320
+     */
321
+    public function hash($type, $path, $raw = false) {
322
+        return $this->getWrapperStorage()->hash($type, $path, $raw);
323
+    }
324
+
325
+    /**
326
+     * see http://php.net/manual/en/function.free_space.php
327
+     *
328
+     * @param string $path
329
+     * @return int
330
+     */
331
+    public function free_space($path) {
332
+        return $this->getWrapperStorage()->free_space($path);
333
+    }
334
+
335
+    /**
336
+     * search for occurrences of $query in file names
337
+     *
338
+     * @param string $query
339
+     * @return array
340
+     */
341
+    public function search($query) {
342
+        return $this->getWrapperStorage()->search($query);
343
+    }
344
+
345
+    /**
346
+     * see http://php.net/manual/en/function.touch.php
347
+     * If the backend does not support the operation, false should be returned
348
+     *
349
+     * @param string $path
350
+     * @param int $mtime
351
+     * @return bool
352
+     */
353
+    public function touch($path, $mtime = null) {
354
+        return $this->getWrapperStorage()->touch($path, $mtime);
355
+    }
356
+
357
+    /**
358
+     * get the path to a local version of the file.
359
+     * The local version of the file can be temporary and doesn't have to be persistent across requests
360
+     *
361
+     * @param string $path
362
+     * @return string
363
+     */
364
+    public function getLocalFile($path) {
365
+        return $this->getWrapperStorage()->getLocalFile($path);
366
+    }
367
+
368
+    /**
369
+     * check if a file or folder has been updated since $time
370
+     *
371
+     * @param string $path
372
+     * @param int $time
373
+     * @return bool
374
+     *
375
+     * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
376
+     * returning true for other changes in the folder is optional
377
+     */
378
+    public function hasUpdated($path, $time) {
379
+        return $this->getWrapperStorage()->hasUpdated($path, $time);
380
+    }
381
+
382
+    /**
383
+     * get a cache instance for the storage
384
+     *
385
+     * @param string $path
386
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
387
+     * @return \OC\Files\Cache\Cache
388
+     */
389
+    public function getCache($path = '', $storage = null) {
390
+        if (!$storage) {
391
+            $storage = $this;
392
+        }
393
+        return $this->getWrapperStorage()->getCache($path, $storage);
394
+    }
395
+
396
+    /**
397
+     * get a scanner instance for the storage
398
+     *
399
+     * @param string $path
400
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
401
+     * @return \OC\Files\Cache\Scanner
402
+     */
403
+    public function getScanner($path = '', $storage = null) {
404
+        if (!$storage) {
405
+            $storage = $this;
406
+        }
407
+        return $this->getWrapperStorage()->getScanner($path, $storage);
408
+    }
409
+
410
+
411
+    /**
412
+     * get the user id of the owner of a file or folder
413
+     *
414
+     * @param string $path
415
+     * @return string
416
+     */
417
+    public function getOwner($path) {
418
+        return $this->getWrapperStorage()->getOwner($path);
419
+    }
420
+
421
+    /**
422
+     * get a watcher instance for the cache
423
+     *
424
+     * @param string $path
425
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
426
+     * @return \OC\Files\Cache\Watcher
427
+     */
428
+    public function getWatcher($path = '', $storage = null) {
429
+        if (!$storage) {
430
+            $storage = $this;
431
+        }
432
+        return $this->getWrapperStorage()->getWatcher($path, $storage);
433
+    }
434
+
435
+    public function getPropagator($storage = null) {
436
+        if (!$storage) {
437
+            $storage = $this;
438
+        }
439
+        return $this->getWrapperStorage()->getPropagator($storage);
440
+    }
441
+
442
+    public function getUpdater($storage = null) {
443
+        if (!$storage) {
444
+            $storage = $this;
445
+        }
446
+        return $this->getWrapperStorage()->getUpdater($storage);
447
+    }
448
+
449
+    /**
450
+     * @return \OC\Files\Cache\Storage
451
+     */
452
+    public function getStorageCache() {
453
+        return $this->getWrapperStorage()->getStorageCache();
454
+    }
455
+
456
+    /**
457
+     * get the ETag for a file or folder
458
+     *
459
+     * @param string $path
460
+     * @return string
461
+     */
462
+    public function getETag($path) {
463
+        return $this->getWrapperStorage()->getETag($path);
464
+    }
465
+
466
+    /**
467
+     * Returns true
468
+     *
469
+     * @return true
470
+     */
471
+    public function test() {
472
+        return $this->getWrapperStorage()->test();
473
+    }
474
+
475
+    /**
476
+     * Returns the wrapped storage's value for isLocal()
477
+     *
478
+     * @return bool wrapped storage's isLocal() value
479
+     */
480
+    public function isLocal() {
481
+        return $this->getWrapperStorage()->isLocal();
482
+    }
483
+
484
+    /**
485
+     * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
486
+     *
487
+     * @param string $class
488
+     * @return bool
489
+     */
490
+    public function instanceOfStorage($class) {
491
+        if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
492
+            // FIXME Temporary fix to keep existing checks working
493
+            $class = '\OCA\Files_Sharing\SharedStorage';
494
+        }
495
+        return is_a($this, $class) or $this->getWrapperStorage()->instanceOfStorage($class);
496
+    }
497
+
498
+    /**
499
+     * Pass any methods custom to specific storage implementations to the wrapped storage
500
+     *
501
+     * @param string $method
502
+     * @param array $args
503
+     * @return mixed
504
+     */
505
+    public function __call($method, $args) {
506
+        return call_user_func_array([$this->getWrapperStorage(), $method], $args);
507
+    }
508
+
509
+    /**
510
+     * A custom storage implementation can return an url for direct download of a give file.
511
+     *
512
+     * For now the returned array can hold the parameter url - in future more attributes might follow.
513
+     *
514
+     * @param string $path
515
+     * @return array
516
+     */
517
+    public function getDirectDownload($path) {
518
+        return $this->getWrapperStorage()->getDirectDownload($path);
519
+    }
520
+
521
+    /**
522
+     * Get availability of the storage
523
+     *
524
+     * @return array [ available, last_checked ]
525
+     */
526
+    public function getAvailability() {
527
+        return $this->getWrapperStorage()->getAvailability();
528
+    }
529
+
530
+    /**
531
+     * Set availability of the storage
532
+     *
533
+     * @param bool $isAvailable
534
+     */
535
+    public function setAvailability($isAvailable) {
536
+        $this->getWrapperStorage()->setAvailability($isAvailable);
537
+    }
538
+
539
+    /**
540
+     * @param string $path the path of the target folder
541
+     * @param string $fileName the name of the file itself
542
+     * @return void
543
+     * @throws InvalidPathException
544
+     */
545
+    public function verifyPath($path, $fileName) {
546
+        $this->getWrapperStorage()->verifyPath($path, $fileName);
547
+    }
548
+
549
+    /**
550
+     * @param IStorage $sourceStorage
551
+     * @param string $sourceInternalPath
552
+     * @param string $targetInternalPath
553
+     * @return bool
554
+     */
555
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
556
+        if ($sourceStorage === $this) {
557
+            return $this->copy($sourceInternalPath, $targetInternalPath);
558
+        }
559
+
560
+        return $this->getWrapperStorage()->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
561
+    }
562
+
563
+    /**
564
+     * @param IStorage $sourceStorage
565
+     * @param string $sourceInternalPath
566
+     * @param string $targetInternalPath
567
+     * @return bool
568
+     */
569
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
570
+        if ($sourceStorage === $this) {
571
+            return $this->rename($sourceInternalPath, $targetInternalPath);
572
+        }
573
+
574
+        return $this->getWrapperStorage()->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
575
+    }
576
+
577
+    /**
578
+     * @param string $path
579
+     * @return array
580
+     */
581
+    public function getMetaData($path) {
582
+        return $this->getWrapperStorage()->getMetaData($path);
583
+    }
584
+
585
+    /**
586
+     * @param string $path
587
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
588
+     * @param \OCP\Lock\ILockingProvider $provider
589
+     * @throws \OCP\Lock\LockedException
590
+     */
591
+    public function acquireLock($path, $type, ILockingProvider $provider) {
592
+        if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
593
+            $this->getWrapperStorage()->acquireLock($path, $type, $provider);
594
+        }
595
+    }
596
+
597
+    /**
598
+     * @param string $path
599
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
600
+     * @param \OCP\Lock\ILockingProvider $provider
601
+     */
602
+    public function releaseLock($path, $type, ILockingProvider $provider) {
603
+        if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
604
+            $this->getWrapperStorage()->releaseLock($path, $type, $provider);
605
+        }
606
+    }
607
+
608
+    /**
609
+     * @param string $path
610
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
611
+     * @param \OCP\Lock\ILockingProvider $provider
612
+     */
613
+    public function changeLock($path, $type, ILockingProvider $provider) {
614
+        if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
615
+            $this->getWrapperStorage()->changeLock($path, $type, $provider);
616
+        }
617
+    }
618
+
619
+    /**
620
+     * @return bool
621
+     */
622
+    public function needsPartFile() {
623
+        return $this->getWrapperStorage()->needsPartFile();
624
+    }
625
+
626
+    public function writeStream(string $path, $stream, int $size = null): int {
627
+        $storage = $this->getWrapperStorage();
628
+        if ($storage->instanceOfStorage(IWriteStreamStorage::class)) {
629
+            /** @var IWriteStreamStorage $storage */
630
+            return $storage->writeStream($path, $stream, $size);
631
+        } else {
632
+            $target = $this->fopen($path, 'w');
633
+            list($count, $result) = \OC_Helper::streamCopy($stream, $target);
634
+            fclose($stream);
635
+            fclose($target);
636
+            return $count;
637
+        }
638
+    }
639 639
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/Wrapper/Encryption.php 1 patch
Indentation   +988 added lines, -988 removed lines patch added patch discarded remove patch
@@ -51,993 +51,993 @@
 block discarded – undo
51 51
 
52 52
 class Encryption extends Wrapper {
53 53
 
54
-	use LocalTempFileTrait;
55
-
56
-	/** @var string */
57
-	private $mountPoint;
58
-
59
-	/** @var \OC\Encryption\Util */
60
-	private $util;
61
-
62
-	/** @var \OCP\Encryption\IManager */
63
-	private $encryptionManager;
64
-
65
-	/** @var \OCP\ILogger */
66
-	private $logger;
67
-
68
-	/** @var string */
69
-	private $uid;
70
-
71
-	/** @var array */
72
-	protected $unencryptedSize;
73
-
74
-	/** @var \OCP\Encryption\IFile */
75
-	private $fileHelper;
76
-
77
-	/** @var IMountPoint */
78
-	private $mount;
79
-
80
-	/** @var IStorage */
81
-	private $keyStorage;
82
-
83
-	/** @var Update */
84
-	private $update;
85
-
86
-	/** @var Manager */
87
-	private $mountManager;
88
-
89
-	/** @var array remember for which path we execute the repair step to avoid recursions */
90
-	private $fixUnencryptedSizeOf = [];
91
-
92
-	/** @var  ArrayCache */
93
-	private $arrayCache;
94
-
95
-	/**
96
-	 * @param array $parameters
97
-	 * @param IManager $encryptionManager
98
-	 * @param Util $util
99
-	 * @param ILogger $logger
100
-	 * @param IFile $fileHelper
101
-	 * @param string $uid
102
-	 * @param IStorage $keyStorage
103
-	 * @param Update $update
104
-	 * @param Manager $mountManager
105
-	 * @param ArrayCache $arrayCache
106
-	 */
107
-	public function __construct(
108
-			$parameters,
109
-			IManager $encryptionManager = null,
110
-			Util $util = null,
111
-			ILogger $logger = null,
112
-			IFile $fileHelper = null,
113
-			$uid = null,
114
-			IStorage $keyStorage = null,
115
-			Update $update = null,
116
-			Manager $mountManager = null,
117
-			ArrayCache $arrayCache = null
118
-		) {
119
-
120
-		$this->mountPoint = $parameters['mountPoint'];
121
-		$this->mount = $parameters['mount'];
122
-		$this->encryptionManager = $encryptionManager;
123
-		$this->util = $util;
124
-		$this->logger = $logger;
125
-		$this->uid = $uid;
126
-		$this->fileHelper = $fileHelper;
127
-		$this->keyStorage = $keyStorage;
128
-		$this->unencryptedSize = [];
129
-		$this->update = $update;
130
-		$this->mountManager = $mountManager;
131
-		$this->arrayCache = $arrayCache;
132
-		parent::__construct($parameters);
133
-	}
134
-
135
-	/**
136
-	 * see http://php.net/manual/en/function.filesize.php
137
-	 * The result for filesize when called on a folder is required to be 0
138
-	 *
139
-	 * @param string $path
140
-	 * @return int
141
-	 */
142
-	public function filesize($path) {
143
-		$fullPath = $this->getFullPath($path);
144
-
145
-		/** @var CacheEntry $info */
146
-		$info = $this->getCache()->get($path);
147
-		if (isset($this->unencryptedSize[$fullPath])) {
148
-			$size = $this->unencryptedSize[$fullPath];
149
-			// update file cache
150
-			if ($info instanceof ICacheEntry) {
151
-				$info = $info->getData();
152
-				$info['encrypted'] = $info['encryptedVersion'];
153
-			} else {
154
-				if (!is_array($info)) {
155
-					$info = [];
156
-				}
157
-				$info['encrypted'] = true;
158
-			}
159
-
160
-			$info['size'] = $size;
161
-			$this->getCache()->put($path, $info);
162
-
163
-			return $size;
164
-		}
165
-
166
-		if (isset($info['fileid']) && $info['encrypted']) {
167
-			return $this->verifyUnencryptedSize($path, $info['size']);
168
-		}
169
-
170
-		return $this->storage->filesize($path);
171
-	}
172
-
173
-	/**
174
-	 * @param string $path
175
-	 * @return array
176
-	 */
177
-	public function getMetaData($path) {
178
-		$data = $this->storage->getMetaData($path);
179
-		if (is_null($data)) {
180
-			return null;
181
-		}
182
-		$fullPath = $this->getFullPath($path);
183
-		$info = $this->getCache()->get($path);
184
-
185
-		if (isset($this->unencryptedSize[$fullPath])) {
186
-			$data['encrypted'] = true;
187
-			$data['size'] = $this->unencryptedSize[$fullPath];
188
-		} else {
189
-			if (isset($info['fileid']) && $info['encrypted']) {
190
-				$data['size'] = $this->verifyUnencryptedSize($path, $info['size']);
191
-				$data['encrypted'] = true;
192
-			}
193
-		}
194
-
195
-		if (isset($info['encryptedVersion']) && $info['encryptedVersion'] > 1) {
196
-			$data['encryptedVersion'] = $info['encryptedVersion'];
197
-		}
198
-
199
-		return $data;
200
-	}
201
-
202
-	/**
203
-	 * see http://php.net/manual/en/function.file_get_contents.php
204
-	 *
205
-	 * @param string $path
206
-	 * @return string
207
-	 */
208
-	public function file_get_contents($path) {
209
-
210
-		$encryptionModule = $this->getEncryptionModule($path);
211
-
212
-		if ($encryptionModule) {
213
-			$handle = $this->fopen($path, "r");
214
-			if (!$handle) {
215
-				return false;
216
-			}
217
-			$data = stream_get_contents($handle);
218
-			fclose($handle);
219
-			return $data;
220
-		}
221
-		return $this->storage->file_get_contents($path);
222
-	}
223
-
224
-	/**
225
-	 * see http://php.net/manual/en/function.file_put_contents.php
226
-	 *
227
-	 * @param string $path
228
-	 * @param string $data
229
-	 * @return bool
230
-	 */
231
-	public function file_put_contents($path, $data) {
232
-		// file put content will always be translated to a stream write
233
-		$handle = $this->fopen($path, 'w');
234
-		if (is_resource($handle)) {
235
-			$written = fwrite($handle, $data);
236
-			fclose($handle);
237
-			return $written;
238
-		}
239
-
240
-		return false;
241
-	}
242
-
243
-	/**
244
-	 * see http://php.net/manual/en/function.unlink.php
245
-	 *
246
-	 * @param string $path
247
-	 * @return bool
248
-	 */
249
-	public function unlink($path) {
250
-		$fullPath = $this->getFullPath($path);
251
-		if ($this->util->isExcluded($fullPath)) {
252
-			return $this->storage->unlink($path);
253
-		}
254
-
255
-		$encryptionModule = $this->getEncryptionModule($path);
256
-		if ($encryptionModule) {
257
-			$this->keyStorage->deleteAllFileKeys($this->getFullPath($path));
258
-		}
259
-
260
-		return $this->storage->unlink($path);
261
-	}
262
-
263
-	/**
264
-	 * see http://php.net/manual/en/function.rename.php
265
-	 *
266
-	 * @param string $path1
267
-	 * @param string $path2
268
-	 * @return bool
269
-	 */
270
-	public function rename($path1, $path2) {
271
-
272
-		$result = $this->storage->rename($path1, $path2);
273
-
274
-		if ($result &&
275
-			// versions always use the keys from the original file, so we can skip
276
-			// this step for versions
277
-			$this->isVersion($path2) === false &&
278
-			$this->encryptionManager->isEnabled()) {
279
-			$source = $this->getFullPath($path1);
280
-			if (!$this->util->isExcluded($source)) {
281
-				$target = $this->getFullPath($path2);
282
-				if (isset($this->unencryptedSize[$source])) {
283
-					$this->unencryptedSize[$target] = $this->unencryptedSize[$source];
284
-				}
285
-				$this->keyStorage->renameKeys($source, $target);
286
-				$module = $this->getEncryptionModule($path2);
287
-				if ($module) {
288
-					$module->update($target, $this->uid, []);
289
-				}
290
-			}
291
-		}
292
-
293
-		return $result;
294
-	}
295
-
296
-	/**
297
-	 * see http://php.net/manual/en/function.rmdir.php
298
-	 *
299
-	 * @param string $path
300
-	 * @return bool
301
-	 */
302
-	public function rmdir($path) {
303
-		$result = $this->storage->rmdir($path);
304
-		$fullPath = $this->getFullPath($path);
305
-		if ($result &&
306
-			$this->util->isExcluded($fullPath) === false &&
307
-			$this->encryptionManager->isEnabled()
308
-		) {
309
-			$this->keyStorage->deleteAllFileKeys($fullPath);
310
-		}
311
-
312
-		return $result;
313
-	}
314
-
315
-	/**
316
-	 * check if a file can be read
317
-	 *
318
-	 * @param string $path
319
-	 * @return bool
320
-	 */
321
-	public function isReadable($path) {
322
-
323
-		$isReadable = true;
324
-
325
-		$metaData = $this->getMetaData($path);
326
-		if (
327
-			!$this->is_dir($path) &&
328
-			isset($metaData['encrypted']) &&
329
-			$metaData['encrypted'] === true
330
-		) {
331
-			$fullPath = $this->getFullPath($path);
332
-			$module = $this->getEncryptionModule($path);
333
-			$isReadable = $module->isReadable($fullPath, $this->uid);
334
-		}
335
-
336
-		return $this->storage->isReadable($path) && $isReadable;
337
-	}
338
-
339
-	/**
340
-	 * see http://php.net/manual/en/function.copy.php
341
-	 *
342
-	 * @param string $path1
343
-	 * @param string $path2
344
-	 * @return bool
345
-	 */
346
-	public function copy($path1, $path2) {
347
-
348
-		$source = $this->getFullPath($path1);
349
-
350
-		if ($this->util->isExcluded($source)) {
351
-			return $this->storage->copy($path1, $path2);
352
-		}
353
-
354
-		// need to stream copy file by file in case we copy between a encrypted
355
-		// and a unencrypted storage
356
-		$this->unlink($path2);
357
-		return $this->copyFromStorage($this, $path1, $path2);
358
-	}
359
-
360
-	/**
361
-	 * see http://php.net/manual/en/function.fopen.php
362
-	 *
363
-	 * @param string $path
364
-	 * @param string $mode
365
-	 * @return resource|bool
366
-	 * @throws GenericEncryptionException
367
-	 * @throws ModuleDoesNotExistsException
368
-	 */
369
-	public function fopen($path, $mode) {
370
-
371
-		// check if the file is stored in the array cache, this means that we
372
-		// copy a file over to the versions folder, in this case we don't want to
373
-		// decrypt it
374
-		if ($this->arrayCache->hasKey('encryption_copy_version_' . $path)) {
375
-			$this->arrayCache->remove('encryption_copy_version_' . $path);
376
-			return $this->storage->fopen($path, $mode);
377
-		}
378
-
379
-		$encryptionEnabled = $this->encryptionManager->isEnabled();
380
-		$shouldEncrypt = false;
381
-		$encryptionModule = null;
382
-		$header = $this->getHeader($path);
383
-		$signed = isset($header['signed']) && $header['signed'] === 'true';
384
-		$fullPath = $this->getFullPath($path);
385
-		$encryptionModuleId = $this->util->getEncryptionModuleId($header);
386
-
387
-		if ($this->util->isExcluded($fullPath) === false) {
388
-
389
-			$size = $unencryptedSize = 0;
390
-			$realFile = $this->util->stripPartialFileExtension($path);
391
-			$targetExists = $this->file_exists($realFile) || $this->file_exists($path);
392
-			$targetIsEncrypted = false;
393
-			if ($targetExists) {
394
-				// in case the file exists we require the explicit module as
395
-				// specified in the file header - otherwise we need to fail hard to
396
-				// prevent data loss on client side
397
-				if (!empty($encryptionModuleId)) {
398
-					$targetIsEncrypted = true;
399
-					$encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
400
-				}
401
-
402
-				if ($this->file_exists($path)) {
403
-					$size = $this->storage->filesize($path);
404
-					$unencryptedSize = $this->filesize($path);
405
-				} else {
406
-					$size = $unencryptedSize = 0;
407
-				}
408
-			}
409
-
410
-			try {
411
-
412
-				if (
413
-					$mode === 'w'
414
-					|| $mode === 'w+'
415
-					|| $mode === 'wb'
416
-					|| $mode === 'wb+'
417
-				) {
418
-					// if we update a encrypted file with a un-encrypted one we change the db flag
419
-					if ($targetIsEncrypted && $encryptionEnabled === false) {
420
-						$cache = $this->storage->getCache();
421
-						if ($cache) {
422
-							$entry = $cache->get($path);
423
-							$cache->update($entry->getId(), ['encrypted' => 0]);
424
-						}
425
-					}
426
-					if ($encryptionEnabled) {
427
-						// if $encryptionModuleId is empty, the default module will be used
428
-						$encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
429
-						$shouldEncrypt = $encryptionModule->shouldEncrypt($fullPath);
430
-						$signed = true;
431
-					}
432
-				} else {
433
-					$info = $this->getCache()->get($path);
434
-					// only get encryption module if we found one in the header
435
-					// or if file should be encrypted according to the file cache
436
-					if (!empty($encryptionModuleId)) {
437
-						$encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
438
-						$shouldEncrypt = true;
439
-					} else if (empty($encryptionModuleId) && $info['encrypted'] === true) {
440
-						// we come from a old installation. No header and/or no module defined
441
-						// but the file is encrypted. In this case we need to use the
442
-						// OC_DEFAULT_MODULE to read the file
443
-						$encryptionModule = $this->encryptionManager->getEncryptionModule('OC_DEFAULT_MODULE');
444
-						$shouldEncrypt = true;
445
-						$targetIsEncrypted = true;
446
-					}
447
-				}
448
-			} catch (ModuleDoesNotExistsException $e) {
449
-				$this->logger->logException($e, [
450
-					'message' => 'Encryption module "' . $encryptionModuleId . '" not found, file will be stored unencrypted',
451
-					'level' => ILogger::WARN,
452
-					'app' => 'core',
453
-				]);
454
-			}
455
-
456
-			// encryption disabled on write of new file and write to existing unencrypted file -> don't encrypt
457
-			if (!$encryptionEnabled || !$this->shouldEncrypt($path)) {
458
-				if (!$targetExists || !$targetIsEncrypted) {
459
-					$shouldEncrypt = false;
460
-				}
461
-			}
462
-
463
-			if ($shouldEncrypt === true && $encryptionModule !== null) {
464
-				$headerSize = $this->getHeaderSize($path);
465
-				$source = $this->storage->fopen($path, $mode);
466
-				if (!is_resource($source)) {
467
-					return false;
468
-				}
469
-				$handle = \OC\Files\Stream\Encryption::wrap($source, $path, $fullPath, $header,
470
-					$this->uid, $encryptionModule, $this->storage, $this, $this->util, $this->fileHelper, $mode,
471
-					$size, $unencryptedSize, $headerSize, $signed);
472
-				return $handle;
473
-			}
474
-
475
-		}
476
-
477
-		return $this->storage->fopen($path, $mode);
478
-	}
479
-
480
-
481
-	/**
482
-	 * perform some plausibility checks if the the unencrypted size is correct.
483
-	 * If not, we calculate the correct unencrypted size and return it
484
-	 *
485
-	 * @param string $path internal path relative to the storage root
486
-	 * @param int $unencryptedSize size of the unencrypted file
487
-	 *
488
-	 * @return int unencrypted size
489
-	 */
490
-	protected function verifyUnencryptedSize($path, $unencryptedSize) {
491
-
492
-		$size = $this->storage->filesize($path);
493
-		$result = $unencryptedSize;
494
-
495
-		if ($unencryptedSize < 0 ||
496
-			($size > 0 && $unencryptedSize === $size)
497
-		) {
498
-			// check if we already calculate the unencrypted size for the
499
-			// given path to avoid recursions
500
-			if (isset($this->fixUnencryptedSizeOf[$this->getFullPath($path)]) === false) {
501
-				$this->fixUnencryptedSizeOf[$this->getFullPath($path)] = true;
502
-				try {
503
-					$result = $this->fixUnencryptedSize($path, $size, $unencryptedSize);
504
-				} catch (\Exception $e) {
505
-					$this->logger->error('Couldn\'t re-calculate unencrypted size for '. $path);
506
-					$this->logger->logException($e);
507
-				}
508
-				unset($this->fixUnencryptedSizeOf[$this->getFullPath($path)]);
509
-			}
510
-		}
511
-
512
-		return $result;
513
-	}
514
-
515
-	/**
516
-	 * calculate the unencrypted size
517
-	 *
518
-	 * @param string $path internal path relative to the storage root
519
-	 * @param int $size size of the physical file
520
-	 * @param int $unencryptedSize size of the unencrypted file
521
-	 *
522
-	 * @return int calculated unencrypted size
523
-	 */
524
-	protected function fixUnencryptedSize($path, $size, $unencryptedSize) {
525
-
526
-		$headerSize = $this->getHeaderSize($path);
527
-		$header = $this->getHeader($path);
528
-		$encryptionModule = $this->getEncryptionModule($path);
529
-
530
-		$stream = $this->storage->fopen($path, 'r');
531
-
532
-		// if we couldn't open the file we return the old unencrypted size
533
-		if (!is_resource($stream)) {
534
-			$this->logger->error('Could not open ' . $path . '. Recalculation of unencrypted size aborted.');
535
-			return $unencryptedSize;
536
-		}
537
-
538
-		$newUnencryptedSize = 0;
539
-		$size -= $headerSize;
540
-		$blockSize = $this->util->getBlockSize();
541
-
542
-		// if a header exists we skip it
543
-		if ($headerSize > 0) {
544
-			fread($stream, $headerSize);
545
-		}
546
-
547
-		// fast path, else the calculation for $lastChunkNr is bogus
548
-		if ($size === 0) {
549
-			return 0;
550
-		}
551
-
552
-		$signed = isset($header['signed']) && $header['signed'] === 'true';
553
-		$unencryptedBlockSize = $encryptionModule->getUnencryptedBlockSize($signed);
554
-
555
-		// calculate last chunk nr
556
-		// next highest is end of chunks, one subtracted is last one
557
-		// we have to read the last chunk, we can't just calculate it (because of padding etc)
558
-
559
-		$lastChunkNr = ceil($size/ $blockSize)-1;
560
-		// calculate last chunk position
561
-		$lastChunkPos = ($lastChunkNr * $blockSize);
562
-		// try to fseek to the last chunk, if it fails we have to read the whole file
563
-		if (@fseek($stream, $lastChunkPos, SEEK_CUR) === 0) {
564
-			$newUnencryptedSize += $lastChunkNr * $unencryptedBlockSize;
565
-		}
566
-
567
-		$lastChunkContentEncrypted='';
568
-		$count = $blockSize;
569
-
570
-		while ($count > 0) {
571
-			$data=fread($stream, $blockSize);
572
-			$count=strlen($data);
573
-			$lastChunkContentEncrypted .= $data;
574
-			if(strlen($lastChunkContentEncrypted) > $blockSize) {
575
-				$newUnencryptedSize += $unencryptedBlockSize;
576
-				$lastChunkContentEncrypted=substr($lastChunkContentEncrypted, $blockSize);
577
-			}
578
-		}
579
-
580
-		fclose($stream);
581
-
582
-		// we have to decrypt the last chunk to get it actual size
583
-		$encryptionModule->begin($this->getFullPath($path), $this->uid, 'r', $header, []);
584
-		$decryptedLastChunk = $encryptionModule->decrypt($lastChunkContentEncrypted, $lastChunkNr . 'end');
585
-		$decryptedLastChunk .= $encryptionModule->end($this->getFullPath($path), $lastChunkNr . 'end');
586
-
587
-		// calc the real file size with the size of the last chunk
588
-		$newUnencryptedSize += strlen($decryptedLastChunk);
589
-
590
-		$this->updateUnencryptedSize($this->getFullPath($path), $newUnencryptedSize);
591
-
592
-		// write to cache if applicable
593
-		$cache = $this->storage->getCache();
594
-		if ($cache) {
595
-			$entry = $cache->get($path);
596
-			$cache->update($entry['fileid'], ['size' => $newUnencryptedSize]);
597
-		}
598
-
599
-		return $newUnencryptedSize;
600
-	}
601
-
602
-	/**
603
-	 * @param Storage\IStorage $sourceStorage
604
-	 * @param string $sourceInternalPath
605
-	 * @param string $targetInternalPath
606
-	 * @param bool $preserveMtime
607
-	 * @return bool
608
-	 */
609
-	public function moveFromStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = true) {
610
-		if ($sourceStorage === $this) {
611
-			return $this->rename($sourceInternalPath, $targetInternalPath);
612
-		}
613
-
614
-		// TODO clean this up once the underlying moveFromStorage in OC\Files\Storage\Wrapper\Common is fixed:
615
-		// - call $this->storage->moveFromStorage() instead of $this->copyBetweenStorage
616
-		// - copy the file cache update from  $this->copyBetweenStorage to this method
617
-		// - copy the copyKeys() call from  $this->copyBetweenStorage to this method
618
-		// - remove $this->copyBetweenStorage
619
-
620
-		if (!$sourceStorage->isDeletable($sourceInternalPath)) {
621
-			return false;
622
-		}
623
-
624
-		$result = $this->copyBetweenStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, true);
625
-		if ($result) {
626
-			if ($sourceStorage->is_dir($sourceInternalPath)) {
627
-				$result &= $sourceStorage->rmdir($sourceInternalPath);
628
-			} else {
629
-				$result &= $sourceStorage->unlink($sourceInternalPath);
630
-			}
631
-		}
632
-		return $result;
633
-	}
634
-
635
-
636
-	/**
637
-	 * @param Storage\IStorage $sourceStorage
638
-	 * @param string $sourceInternalPath
639
-	 * @param string $targetInternalPath
640
-	 * @param bool $preserveMtime
641
-	 * @param bool $isRename
642
-	 * @return bool
643
-	 */
644
-	public function copyFromStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false, $isRename = false) {
645
-
646
-		// TODO clean this up once the underlying moveFromStorage in OC\Files\Storage\Wrapper\Common is fixed:
647
-		// - call $this->storage->copyFromStorage() instead of $this->copyBetweenStorage
648
-		// - copy the file cache update from  $this->copyBetweenStorage to this method
649
-		// - copy the copyKeys() call from  $this->copyBetweenStorage to this method
650
-		// - remove $this->copyBetweenStorage
651
-
652
-		return $this->copyBetweenStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename);
653
-	}
654
-
655
-	/**
656
-	 * Update the encrypted cache version in the database
657
-	 *
658
-	 * @param Storage\IStorage $sourceStorage
659
-	 * @param string $sourceInternalPath
660
-	 * @param string $targetInternalPath
661
-	 * @param bool $isRename
662
-	 * @param bool $keepEncryptionVersion
663
-	 */
664
-	private function updateEncryptedVersion(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, $keepEncryptionVersion) {
665
-		$isEncrypted = $this->encryptionManager->isEnabled() && $this->shouldEncrypt($targetInternalPath);
666
-		$cacheInformation = [
667
-			'encrypted' => $isEncrypted,
668
-		];
669
-		if($isEncrypted) {
670
-			$encryptedVersion = $sourceStorage->getCache()->get($sourceInternalPath)['encryptedVersion'];
671
-
672
-			// In case of a move operation from an unencrypted to an encrypted
673
-			// storage the old encrypted version would stay with "0" while the
674
-			// correct value would be "1". Thus we manually set the value to "1"
675
-			// for those cases.
676
-			// See also https://github.com/owncloud/core/issues/23078
677
-			if($encryptedVersion === 0 || !$keepEncryptionVersion) {
678
-				$encryptedVersion = 1;
679
-			}
680
-
681
-			$cacheInformation['encryptedVersion'] = $encryptedVersion;
682
-		}
683
-
684
-		// in case of a rename we need to manipulate the source cache because
685
-		// this information will be kept for the new target
686
-		if ($isRename) {
687
-			$sourceStorage->getCache()->put($sourceInternalPath, $cacheInformation);
688
-		} else {
689
-			$this->getCache()->put($targetInternalPath, $cacheInformation);
690
-		}
691
-	}
692
-
693
-	/**
694
-	 * copy file between two storages
695
-	 *
696
-	 * @param Storage\IStorage $sourceStorage
697
-	 * @param string $sourceInternalPath
698
-	 * @param string $targetInternalPath
699
-	 * @param bool $preserveMtime
700
-	 * @param bool $isRename
701
-	 * @return bool
702
-	 * @throws \Exception
703
-	 */
704
-	private function copyBetweenStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename) {
705
-
706
-		// for versions we have nothing to do, because versions should always use the
707
-		// key from the original file. Just create a 1:1 copy and done
708
-		if ($this->isVersion($targetInternalPath) ||
709
-			$this->isVersion($sourceInternalPath)) {
710
-			// remember that we try to create a version so that we can detect it during
711
-			// fopen($sourceInternalPath) and by-pass the encryption in order to
712
-			// create a 1:1 copy of the file
713
-			$this->arrayCache->set('encryption_copy_version_' . $sourceInternalPath, true);
714
-			$result = $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
715
-			$this->arrayCache->remove('encryption_copy_version_' . $sourceInternalPath);
716
-			if ($result) {
717
-				$info = $this->getCache('', $sourceStorage)->get($sourceInternalPath);
718
-				// make sure that we update the unencrypted size for the version
719
-				if (isset($info['encrypted']) && $info['encrypted'] === true) {
720
-					$this->updateUnencryptedSize(
721
-						$this->getFullPath($targetInternalPath),
722
-						$info['size']
723
-					);
724
-				}
725
-				$this->updateEncryptedVersion($sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, true);
726
-			}
727
-			return $result;
728
-		}
729
-
730
-		// first copy the keys that we reuse the existing file key on the target location
731
-		// and don't create a new one which would break versions for example.
732
-		$mount = $this->mountManager->findByStorageId($sourceStorage->getId());
733
-		if (count($mount) === 1) {
734
-			$mountPoint = $mount[0]->getMountPoint();
735
-			$source = $mountPoint . '/' . $sourceInternalPath;
736
-			$target = $this->getFullPath($targetInternalPath);
737
-			$this->copyKeys($source, $target);
738
-		} else {
739
-			$this->logger->error('Could not find mount point, can\'t keep encryption keys');
740
-		}
741
-
742
-		if ($sourceStorage->is_dir($sourceInternalPath)) {
743
-			$dh = $sourceStorage->opendir($sourceInternalPath);
744
-			$result = $this->mkdir($targetInternalPath);
745
-			if (is_resource($dh)) {
746
-				while ($result and ($file = readdir($dh)) !== false) {
747
-					if (!Filesystem::isIgnoredDir($file)) {
748
-						$result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file, false, $isRename);
749
-					}
750
-				}
751
-			}
752
-		} else {
753
-			try {
754
-				$source = $sourceStorage->fopen($sourceInternalPath, 'r');
755
-				$target = $this->fopen($targetInternalPath, 'w');
756
-				list(, $result) = \OC_Helper::streamCopy($source, $target);
757
-				fclose($source);
758
-				fclose($target);
759
-			} catch (\Exception $e) {
760
-				fclose($source);
761
-				fclose($target);
762
-				throw $e;
763
-			}
764
-			if($result) {
765
-				if ($preserveMtime) {
766
-					$this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath));
767
-				}
768
-				$this->updateEncryptedVersion($sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, false);
769
-			} else {
770
-				// delete partially written target file
771
-				$this->unlink($targetInternalPath);
772
-				// delete cache entry that was created by fopen
773
-				$this->getCache()->remove($targetInternalPath);
774
-			}
775
-		}
776
-		return (bool)$result;
777
-
778
-	}
779
-
780
-	/**
781
-	 * get the path to a local version of the file.
782
-	 * The local version of the file can be temporary and doesn't have to be persistent across requests
783
-	 *
784
-	 * @param string $path
785
-	 * @return string
786
-	 */
787
-	public function getLocalFile($path) {
788
-		if ($this->encryptionManager->isEnabled()) {
789
-			$cachedFile = $this->getCachedFile($path);
790
-			if (is_string($cachedFile)) {
791
-				return $cachedFile;
792
-			}
793
-		}
794
-		return $this->storage->getLocalFile($path);
795
-	}
796
-
797
-	/**
798
-	 * Returns the wrapped storage's value for isLocal()
799
-	 *
800
-	 * @return bool wrapped storage's isLocal() value
801
-	 */
802
-	public function isLocal() {
803
-		if ($this->encryptionManager->isEnabled()) {
804
-			return false;
805
-		}
806
-		return $this->storage->isLocal();
807
-	}
808
-
809
-	/**
810
-	 * see http://php.net/manual/en/function.stat.php
811
-	 * only the following keys are required in the result: size and mtime
812
-	 *
813
-	 * @param string $path
814
-	 * @return array
815
-	 */
816
-	public function stat($path) {
817
-		$stat = $this->storage->stat($path);
818
-		$fileSize = $this->filesize($path);
819
-		$stat['size'] = $fileSize;
820
-		$stat[7] = $fileSize;
821
-		return $stat;
822
-	}
823
-
824
-	/**
825
-	 * see http://php.net/manual/en/function.hash.php
826
-	 *
827
-	 * @param string $type
828
-	 * @param string $path
829
-	 * @param bool $raw
830
-	 * @return string
831
-	 */
832
-	public function hash($type, $path, $raw = false) {
833
-		$fh = $this->fopen($path, 'rb');
834
-		$ctx = hash_init($type);
835
-		hash_update_stream($ctx, $fh);
836
-		fclose($fh);
837
-		return hash_final($ctx, $raw);
838
-	}
839
-
840
-	/**
841
-	 * return full path, including mount point
842
-	 *
843
-	 * @param string $path relative to mount point
844
-	 * @return string full path including mount point
845
-	 */
846
-	protected function getFullPath($path) {
847
-		return Filesystem::normalizePath($this->mountPoint . '/' . $path);
848
-	}
849
-
850
-	/**
851
-	 * read first block of encrypted file, typically this will contain the
852
-	 * encryption header
853
-	 *
854
-	 * @param string $path
855
-	 * @return string
856
-	 */
857
-	protected function readFirstBlock($path) {
858
-		$firstBlock = '';
859
-		if ($this->storage->file_exists($path)) {
860
-			$handle = $this->storage->fopen($path, 'r');
861
-			$firstBlock = fread($handle, $this->util->getHeaderSize());
862
-			fclose($handle);
863
-		}
864
-		return $firstBlock;
865
-	}
866
-
867
-	/**
868
-	 * return header size of given file
869
-	 *
870
-	 * @param string $path
871
-	 * @return int
872
-	 */
873
-	protected function getHeaderSize($path) {
874
-		$headerSize = 0;
875
-		$realFile = $this->util->stripPartialFileExtension($path);
876
-		if ($this->storage->file_exists($realFile)) {
877
-			$path = $realFile;
878
-		}
879
-		$firstBlock = $this->readFirstBlock($path);
880
-
881
-		if (substr($firstBlock, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) {
882
-			$headerSize = $this->util->getHeaderSize();
883
-		}
884
-
885
-		return $headerSize;
886
-	}
887
-
888
-	/**
889
-	 * parse raw header to array
890
-	 *
891
-	 * @param string $rawHeader
892
-	 * @return array
893
-	 */
894
-	protected function parseRawHeader($rawHeader) {
895
-		$result = [];
896
-		if (substr($rawHeader, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) {
897
-			$header = $rawHeader;
898
-			$endAt = strpos($header, Util::HEADER_END);
899
-			if ($endAt !== false) {
900
-				$header = substr($header, 0, $endAt + strlen(Util::HEADER_END));
901
-
902
-				// +1 to not start with an ':' which would result in empty element at the beginning
903
-				$exploded = explode(':', substr($header, strlen(Util::HEADER_START)+1));
904
-
905
-				$element = array_shift($exploded);
906
-				while ($element !== Util::HEADER_END) {
907
-					$result[$element] = array_shift($exploded);
908
-					$element = array_shift($exploded);
909
-				}
910
-			}
911
-		}
912
-
913
-		return $result;
914
-	}
915
-
916
-	/**
917
-	 * read header from file
918
-	 *
919
-	 * @param string $path
920
-	 * @return array
921
-	 */
922
-	protected function getHeader($path) {
923
-		$realFile = $this->util->stripPartialFileExtension($path);
924
-		$exists = $this->storage->file_exists($realFile);
925
-		if ($exists) {
926
-			$path = $realFile;
927
-		}
928
-
929
-		$firstBlock = $this->readFirstBlock($path);
930
-		$result = $this->parseRawHeader($firstBlock);
931
-
932
-		// if the header doesn't contain a encryption module we check if it is a
933
-		// legacy file. If true, we add the default encryption module
934
-		if (!isset($result[Util::HEADER_ENCRYPTION_MODULE_KEY])) {
935
-			if (!empty($result)) {
936
-				$result[Util::HEADER_ENCRYPTION_MODULE_KEY] = 'OC_DEFAULT_MODULE';
937
-			} else if ($exists) {
938
-				// if the header was empty we have to check first if it is a encrypted file at all
939
-				// We would do query to filecache only if we know that entry in filecache exists
940
-				$info = $this->getCache()->get($path);
941
-				if (isset($info['encrypted']) && $info['encrypted'] === true) {
942
-					$result[Util::HEADER_ENCRYPTION_MODULE_KEY] = 'OC_DEFAULT_MODULE';
943
-				}
944
-			}
945
-		}
946
-
947
-		return $result;
948
-	}
949
-
950
-	/**
951
-	 * read encryption module needed to read/write the file located at $path
952
-	 *
953
-	 * @param string $path
954
-	 * @return null|\OCP\Encryption\IEncryptionModule
955
-	 * @throws ModuleDoesNotExistsException
956
-	 * @throws \Exception
957
-	 */
958
-	protected function getEncryptionModule($path) {
959
-		$encryptionModule = null;
960
-		$header = $this->getHeader($path);
961
-		$encryptionModuleId = $this->util->getEncryptionModuleId($header);
962
-		if (!empty($encryptionModuleId)) {
963
-			try {
964
-				$encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
965
-			} catch (ModuleDoesNotExistsException $e) {
966
-				$this->logger->critical('Encryption module defined in "' . $path . '" not loaded!');
967
-				throw $e;
968
-			}
969
-		}
970
-
971
-		return $encryptionModule;
972
-	}
973
-
974
-	/**
975
-	 * @param string $path
976
-	 * @param int $unencryptedSize
977
-	 */
978
-	public function updateUnencryptedSize($path, $unencryptedSize) {
979
-		$this->unencryptedSize[$path] = $unencryptedSize;
980
-	}
981
-
982
-	/**
983
-	 * copy keys to new location
984
-	 *
985
-	 * @param string $source path relative to data/
986
-	 * @param string $target path relative to data/
987
-	 * @return bool
988
-	 */
989
-	protected function copyKeys($source, $target) {
990
-		if (!$this->util->isExcluded($source)) {
991
-			return $this->keyStorage->copyKeys($source, $target);
992
-		}
993
-
994
-		return false;
995
-	}
996
-
997
-	/**
998
-	 * check if path points to a files version
999
-	 *
1000
-	 * @param $path
1001
-	 * @return bool
1002
-	 */
1003
-	protected function isVersion($path) {
1004
-		$normalized = Filesystem::normalizePath($path);
1005
-		return substr($normalized, 0, strlen('/files_versions/')) === '/files_versions/';
1006
-	}
1007
-
1008
-	/**
1009
-	 * check if the given storage should be encrypted or not
1010
-	 *
1011
-	 * @param $path
1012
-	 * @return bool
1013
-	 */
1014
-	protected function shouldEncrypt($path) {
1015
-		$fullPath = $this->getFullPath($path);
1016
-		$mountPointConfig = $this->mount->getOption('encrypt', true);
1017
-		if ($mountPointConfig === false) {
1018
-			return false;
1019
-		}
1020
-
1021
-		try {
1022
-			$encryptionModule = $this->getEncryptionModule($fullPath);
1023
-		} catch (ModuleDoesNotExistsException $e) {
1024
-			return false;
1025
-		}
1026
-
1027
-		if ($encryptionModule === null) {
1028
-			$encryptionModule = $this->encryptionManager->getEncryptionModule();
1029
-		}
1030
-
1031
-		return $encryptionModule->shouldEncrypt($fullPath);
1032
-
1033
-	}
1034
-
1035
-	public function writeStream(string $path, $stream, int $size = null): int {
1036
-		// always fall back to fopen
1037
-		$target = $this->fopen($path, 'w');
1038
-		list($count, $result) = \OC_Helper::streamCopy($stream, $target);
1039
-		fclose($target);
1040
-		return $count;
1041
-	}
54
+    use LocalTempFileTrait;
55
+
56
+    /** @var string */
57
+    private $mountPoint;
58
+
59
+    /** @var \OC\Encryption\Util */
60
+    private $util;
61
+
62
+    /** @var \OCP\Encryption\IManager */
63
+    private $encryptionManager;
64
+
65
+    /** @var \OCP\ILogger */
66
+    private $logger;
67
+
68
+    /** @var string */
69
+    private $uid;
70
+
71
+    /** @var array */
72
+    protected $unencryptedSize;
73
+
74
+    /** @var \OCP\Encryption\IFile */
75
+    private $fileHelper;
76
+
77
+    /** @var IMountPoint */
78
+    private $mount;
79
+
80
+    /** @var IStorage */
81
+    private $keyStorage;
82
+
83
+    /** @var Update */
84
+    private $update;
85
+
86
+    /** @var Manager */
87
+    private $mountManager;
88
+
89
+    /** @var array remember for which path we execute the repair step to avoid recursions */
90
+    private $fixUnencryptedSizeOf = [];
91
+
92
+    /** @var  ArrayCache */
93
+    private $arrayCache;
94
+
95
+    /**
96
+     * @param array $parameters
97
+     * @param IManager $encryptionManager
98
+     * @param Util $util
99
+     * @param ILogger $logger
100
+     * @param IFile $fileHelper
101
+     * @param string $uid
102
+     * @param IStorage $keyStorage
103
+     * @param Update $update
104
+     * @param Manager $mountManager
105
+     * @param ArrayCache $arrayCache
106
+     */
107
+    public function __construct(
108
+            $parameters,
109
+            IManager $encryptionManager = null,
110
+            Util $util = null,
111
+            ILogger $logger = null,
112
+            IFile $fileHelper = null,
113
+            $uid = null,
114
+            IStorage $keyStorage = null,
115
+            Update $update = null,
116
+            Manager $mountManager = null,
117
+            ArrayCache $arrayCache = null
118
+        ) {
119
+
120
+        $this->mountPoint = $parameters['mountPoint'];
121
+        $this->mount = $parameters['mount'];
122
+        $this->encryptionManager = $encryptionManager;
123
+        $this->util = $util;
124
+        $this->logger = $logger;
125
+        $this->uid = $uid;
126
+        $this->fileHelper = $fileHelper;
127
+        $this->keyStorage = $keyStorage;
128
+        $this->unencryptedSize = [];
129
+        $this->update = $update;
130
+        $this->mountManager = $mountManager;
131
+        $this->arrayCache = $arrayCache;
132
+        parent::__construct($parameters);
133
+    }
134
+
135
+    /**
136
+     * see http://php.net/manual/en/function.filesize.php
137
+     * The result for filesize when called on a folder is required to be 0
138
+     *
139
+     * @param string $path
140
+     * @return int
141
+     */
142
+    public function filesize($path) {
143
+        $fullPath = $this->getFullPath($path);
144
+
145
+        /** @var CacheEntry $info */
146
+        $info = $this->getCache()->get($path);
147
+        if (isset($this->unencryptedSize[$fullPath])) {
148
+            $size = $this->unencryptedSize[$fullPath];
149
+            // update file cache
150
+            if ($info instanceof ICacheEntry) {
151
+                $info = $info->getData();
152
+                $info['encrypted'] = $info['encryptedVersion'];
153
+            } else {
154
+                if (!is_array($info)) {
155
+                    $info = [];
156
+                }
157
+                $info['encrypted'] = true;
158
+            }
159
+
160
+            $info['size'] = $size;
161
+            $this->getCache()->put($path, $info);
162
+
163
+            return $size;
164
+        }
165
+
166
+        if (isset($info['fileid']) && $info['encrypted']) {
167
+            return $this->verifyUnencryptedSize($path, $info['size']);
168
+        }
169
+
170
+        return $this->storage->filesize($path);
171
+    }
172
+
173
+    /**
174
+     * @param string $path
175
+     * @return array
176
+     */
177
+    public function getMetaData($path) {
178
+        $data = $this->storage->getMetaData($path);
179
+        if (is_null($data)) {
180
+            return null;
181
+        }
182
+        $fullPath = $this->getFullPath($path);
183
+        $info = $this->getCache()->get($path);
184
+
185
+        if (isset($this->unencryptedSize[$fullPath])) {
186
+            $data['encrypted'] = true;
187
+            $data['size'] = $this->unencryptedSize[$fullPath];
188
+        } else {
189
+            if (isset($info['fileid']) && $info['encrypted']) {
190
+                $data['size'] = $this->verifyUnencryptedSize($path, $info['size']);
191
+                $data['encrypted'] = true;
192
+            }
193
+        }
194
+
195
+        if (isset($info['encryptedVersion']) && $info['encryptedVersion'] > 1) {
196
+            $data['encryptedVersion'] = $info['encryptedVersion'];
197
+        }
198
+
199
+        return $data;
200
+    }
201
+
202
+    /**
203
+     * see http://php.net/manual/en/function.file_get_contents.php
204
+     *
205
+     * @param string $path
206
+     * @return string
207
+     */
208
+    public function file_get_contents($path) {
209
+
210
+        $encryptionModule = $this->getEncryptionModule($path);
211
+
212
+        if ($encryptionModule) {
213
+            $handle = $this->fopen($path, "r");
214
+            if (!$handle) {
215
+                return false;
216
+            }
217
+            $data = stream_get_contents($handle);
218
+            fclose($handle);
219
+            return $data;
220
+        }
221
+        return $this->storage->file_get_contents($path);
222
+    }
223
+
224
+    /**
225
+     * see http://php.net/manual/en/function.file_put_contents.php
226
+     *
227
+     * @param string $path
228
+     * @param string $data
229
+     * @return bool
230
+     */
231
+    public function file_put_contents($path, $data) {
232
+        // file put content will always be translated to a stream write
233
+        $handle = $this->fopen($path, 'w');
234
+        if (is_resource($handle)) {
235
+            $written = fwrite($handle, $data);
236
+            fclose($handle);
237
+            return $written;
238
+        }
239
+
240
+        return false;
241
+    }
242
+
243
+    /**
244
+     * see http://php.net/manual/en/function.unlink.php
245
+     *
246
+     * @param string $path
247
+     * @return bool
248
+     */
249
+    public function unlink($path) {
250
+        $fullPath = $this->getFullPath($path);
251
+        if ($this->util->isExcluded($fullPath)) {
252
+            return $this->storage->unlink($path);
253
+        }
254
+
255
+        $encryptionModule = $this->getEncryptionModule($path);
256
+        if ($encryptionModule) {
257
+            $this->keyStorage->deleteAllFileKeys($this->getFullPath($path));
258
+        }
259
+
260
+        return $this->storage->unlink($path);
261
+    }
262
+
263
+    /**
264
+     * see http://php.net/manual/en/function.rename.php
265
+     *
266
+     * @param string $path1
267
+     * @param string $path2
268
+     * @return bool
269
+     */
270
+    public function rename($path1, $path2) {
271
+
272
+        $result = $this->storage->rename($path1, $path2);
273
+
274
+        if ($result &&
275
+            // versions always use the keys from the original file, so we can skip
276
+            // this step for versions
277
+            $this->isVersion($path2) === false &&
278
+            $this->encryptionManager->isEnabled()) {
279
+            $source = $this->getFullPath($path1);
280
+            if (!$this->util->isExcluded($source)) {
281
+                $target = $this->getFullPath($path2);
282
+                if (isset($this->unencryptedSize[$source])) {
283
+                    $this->unencryptedSize[$target] = $this->unencryptedSize[$source];
284
+                }
285
+                $this->keyStorage->renameKeys($source, $target);
286
+                $module = $this->getEncryptionModule($path2);
287
+                if ($module) {
288
+                    $module->update($target, $this->uid, []);
289
+                }
290
+            }
291
+        }
292
+
293
+        return $result;
294
+    }
295
+
296
+    /**
297
+     * see http://php.net/manual/en/function.rmdir.php
298
+     *
299
+     * @param string $path
300
+     * @return bool
301
+     */
302
+    public function rmdir($path) {
303
+        $result = $this->storage->rmdir($path);
304
+        $fullPath = $this->getFullPath($path);
305
+        if ($result &&
306
+            $this->util->isExcluded($fullPath) === false &&
307
+            $this->encryptionManager->isEnabled()
308
+        ) {
309
+            $this->keyStorage->deleteAllFileKeys($fullPath);
310
+        }
311
+
312
+        return $result;
313
+    }
314
+
315
+    /**
316
+     * check if a file can be read
317
+     *
318
+     * @param string $path
319
+     * @return bool
320
+     */
321
+    public function isReadable($path) {
322
+
323
+        $isReadable = true;
324
+
325
+        $metaData = $this->getMetaData($path);
326
+        if (
327
+            !$this->is_dir($path) &&
328
+            isset($metaData['encrypted']) &&
329
+            $metaData['encrypted'] === true
330
+        ) {
331
+            $fullPath = $this->getFullPath($path);
332
+            $module = $this->getEncryptionModule($path);
333
+            $isReadable = $module->isReadable($fullPath, $this->uid);
334
+        }
335
+
336
+        return $this->storage->isReadable($path) && $isReadable;
337
+    }
338
+
339
+    /**
340
+     * see http://php.net/manual/en/function.copy.php
341
+     *
342
+     * @param string $path1
343
+     * @param string $path2
344
+     * @return bool
345
+     */
346
+    public function copy($path1, $path2) {
347
+
348
+        $source = $this->getFullPath($path1);
349
+
350
+        if ($this->util->isExcluded($source)) {
351
+            return $this->storage->copy($path1, $path2);
352
+        }
353
+
354
+        // need to stream copy file by file in case we copy between a encrypted
355
+        // and a unencrypted storage
356
+        $this->unlink($path2);
357
+        return $this->copyFromStorage($this, $path1, $path2);
358
+    }
359
+
360
+    /**
361
+     * see http://php.net/manual/en/function.fopen.php
362
+     *
363
+     * @param string $path
364
+     * @param string $mode
365
+     * @return resource|bool
366
+     * @throws GenericEncryptionException
367
+     * @throws ModuleDoesNotExistsException
368
+     */
369
+    public function fopen($path, $mode) {
370
+
371
+        // check if the file is stored in the array cache, this means that we
372
+        // copy a file over to the versions folder, in this case we don't want to
373
+        // decrypt it
374
+        if ($this->arrayCache->hasKey('encryption_copy_version_' . $path)) {
375
+            $this->arrayCache->remove('encryption_copy_version_' . $path);
376
+            return $this->storage->fopen($path, $mode);
377
+        }
378
+
379
+        $encryptionEnabled = $this->encryptionManager->isEnabled();
380
+        $shouldEncrypt = false;
381
+        $encryptionModule = null;
382
+        $header = $this->getHeader($path);
383
+        $signed = isset($header['signed']) && $header['signed'] === 'true';
384
+        $fullPath = $this->getFullPath($path);
385
+        $encryptionModuleId = $this->util->getEncryptionModuleId($header);
386
+
387
+        if ($this->util->isExcluded($fullPath) === false) {
388
+
389
+            $size = $unencryptedSize = 0;
390
+            $realFile = $this->util->stripPartialFileExtension($path);
391
+            $targetExists = $this->file_exists($realFile) || $this->file_exists($path);
392
+            $targetIsEncrypted = false;
393
+            if ($targetExists) {
394
+                // in case the file exists we require the explicit module as
395
+                // specified in the file header - otherwise we need to fail hard to
396
+                // prevent data loss on client side
397
+                if (!empty($encryptionModuleId)) {
398
+                    $targetIsEncrypted = true;
399
+                    $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
400
+                }
401
+
402
+                if ($this->file_exists($path)) {
403
+                    $size = $this->storage->filesize($path);
404
+                    $unencryptedSize = $this->filesize($path);
405
+                } else {
406
+                    $size = $unencryptedSize = 0;
407
+                }
408
+            }
409
+
410
+            try {
411
+
412
+                if (
413
+                    $mode === 'w'
414
+                    || $mode === 'w+'
415
+                    || $mode === 'wb'
416
+                    || $mode === 'wb+'
417
+                ) {
418
+                    // if we update a encrypted file with a un-encrypted one we change the db flag
419
+                    if ($targetIsEncrypted && $encryptionEnabled === false) {
420
+                        $cache = $this->storage->getCache();
421
+                        if ($cache) {
422
+                            $entry = $cache->get($path);
423
+                            $cache->update($entry->getId(), ['encrypted' => 0]);
424
+                        }
425
+                    }
426
+                    if ($encryptionEnabled) {
427
+                        // if $encryptionModuleId is empty, the default module will be used
428
+                        $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
429
+                        $shouldEncrypt = $encryptionModule->shouldEncrypt($fullPath);
430
+                        $signed = true;
431
+                    }
432
+                } else {
433
+                    $info = $this->getCache()->get($path);
434
+                    // only get encryption module if we found one in the header
435
+                    // or if file should be encrypted according to the file cache
436
+                    if (!empty($encryptionModuleId)) {
437
+                        $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
438
+                        $shouldEncrypt = true;
439
+                    } else if (empty($encryptionModuleId) && $info['encrypted'] === true) {
440
+                        // we come from a old installation. No header and/or no module defined
441
+                        // but the file is encrypted. In this case we need to use the
442
+                        // OC_DEFAULT_MODULE to read the file
443
+                        $encryptionModule = $this->encryptionManager->getEncryptionModule('OC_DEFAULT_MODULE');
444
+                        $shouldEncrypt = true;
445
+                        $targetIsEncrypted = true;
446
+                    }
447
+                }
448
+            } catch (ModuleDoesNotExistsException $e) {
449
+                $this->logger->logException($e, [
450
+                    'message' => 'Encryption module "' . $encryptionModuleId . '" not found, file will be stored unencrypted',
451
+                    'level' => ILogger::WARN,
452
+                    'app' => 'core',
453
+                ]);
454
+            }
455
+
456
+            // encryption disabled on write of new file and write to existing unencrypted file -> don't encrypt
457
+            if (!$encryptionEnabled || !$this->shouldEncrypt($path)) {
458
+                if (!$targetExists || !$targetIsEncrypted) {
459
+                    $shouldEncrypt = false;
460
+                }
461
+            }
462
+
463
+            if ($shouldEncrypt === true && $encryptionModule !== null) {
464
+                $headerSize = $this->getHeaderSize($path);
465
+                $source = $this->storage->fopen($path, $mode);
466
+                if (!is_resource($source)) {
467
+                    return false;
468
+                }
469
+                $handle = \OC\Files\Stream\Encryption::wrap($source, $path, $fullPath, $header,
470
+                    $this->uid, $encryptionModule, $this->storage, $this, $this->util, $this->fileHelper, $mode,
471
+                    $size, $unencryptedSize, $headerSize, $signed);
472
+                return $handle;
473
+            }
474
+
475
+        }
476
+
477
+        return $this->storage->fopen($path, $mode);
478
+    }
479
+
480
+
481
+    /**
482
+     * perform some plausibility checks if the the unencrypted size is correct.
483
+     * If not, we calculate the correct unencrypted size and return it
484
+     *
485
+     * @param string $path internal path relative to the storage root
486
+     * @param int $unencryptedSize size of the unencrypted file
487
+     *
488
+     * @return int unencrypted size
489
+     */
490
+    protected function verifyUnencryptedSize($path, $unencryptedSize) {
491
+
492
+        $size = $this->storage->filesize($path);
493
+        $result = $unencryptedSize;
494
+
495
+        if ($unencryptedSize < 0 ||
496
+            ($size > 0 && $unencryptedSize === $size)
497
+        ) {
498
+            // check if we already calculate the unencrypted size for the
499
+            // given path to avoid recursions
500
+            if (isset($this->fixUnencryptedSizeOf[$this->getFullPath($path)]) === false) {
501
+                $this->fixUnencryptedSizeOf[$this->getFullPath($path)] = true;
502
+                try {
503
+                    $result = $this->fixUnencryptedSize($path, $size, $unencryptedSize);
504
+                } catch (\Exception $e) {
505
+                    $this->logger->error('Couldn\'t re-calculate unencrypted size for '. $path);
506
+                    $this->logger->logException($e);
507
+                }
508
+                unset($this->fixUnencryptedSizeOf[$this->getFullPath($path)]);
509
+            }
510
+        }
511
+
512
+        return $result;
513
+    }
514
+
515
+    /**
516
+     * calculate the unencrypted size
517
+     *
518
+     * @param string $path internal path relative to the storage root
519
+     * @param int $size size of the physical file
520
+     * @param int $unencryptedSize size of the unencrypted file
521
+     *
522
+     * @return int calculated unencrypted size
523
+     */
524
+    protected function fixUnencryptedSize($path, $size, $unencryptedSize) {
525
+
526
+        $headerSize = $this->getHeaderSize($path);
527
+        $header = $this->getHeader($path);
528
+        $encryptionModule = $this->getEncryptionModule($path);
529
+
530
+        $stream = $this->storage->fopen($path, 'r');
531
+
532
+        // if we couldn't open the file we return the old unencrypted size
533
+        if (!is_resource($stream)) {
534
+            $this->logger->error('Could not open ' . $path . '. Recalculation of unencrypted size aborted.');
535
+            return $unencryptedSize;
536
+        }
537
+
538
+        $newUnencryptedSize = 0;
539
+        $size -= $headerSize;
540
+        $blockSize = $this->util->getBlockSize();
541
+
542
+        // if a header exists we skip it
543
+        if ($headerSize > 0) {
544
+            fread($stream, $headerSize);
545
+        }
546
+
547
+        // fast path, else the calculation for $lastChunkNr is bogus
548
+        if ($size === 0) {
549
+            return 0;
550
+        }
551
+
552
+        $signed = isset($header['signed']) && $header['signed'] === 'true';
553
+        $unencryptedBlockSize = $encryptionModule->getUnencryptedBlockSize($signed);
554
+
555
+        // calculate last chunk nr
556
+        // next highest is end of chunks, one subtracted is last one
557
+        // we have to read the last chunk, we can't just calculate it (because of padding etc)
558
+
559
+        $lastChunkNr = ceil($size/ $blockSize)-1;
560
+        // calculate last chunk position
561
+        $lastChunkPos = ($lastChunkNr * $blockSize);
562
+        // try to fseek to the last chunk, if it fails we have to read the whole file
563
+        if (@fseek($stream, $lastChunkPos, SEEK_CUR) === 0) {
564
+            $newUnencryptedSize += $lastChunkNr * $unencryptedBlockSize;
565
+        }
566
+
567
+        $lastChunkContentEncrypted='';
568
+        $count = $blockSize;
569
+
570
+        while ($count > 0) {
571
+            $data=fread($stream, $blockSize);
572
+            $count=strlen($data);
573
+            $lastChunkContentEncrypted .= $data;
574
+            if(strlen($lastChunkContentEncrypted) > $blockSize) {
575
+                $newUnencryptedSize += $unencryptedBlockSize;
576
+                $lastChunkContentEncrypted=substr($lastChunkContentEncrypted, $blockSize);
577
+            }
578
+        }
579
+
580
+        fclose($stream);
581
+
582
+        // we have to decrypt the last chunk to get it actual size
583
+        $encryptionModule->begin($this->getFullPath($path), $this->uid, 'r', $header, []);
584
+        $decryptedLastChunk = $encryptionModule->decrypt($lastChunkContentEncrypted, $lastChunkNr . 'end');
585
+        $decryptedLastChunk .= $encryptionModule->end($this->getFullPath($path), $lastChunkNr . 'end');
586
+
587
+        // calc the real file size with the size of the last chunk
588
+        $newUnencryptedSize += strlen($decryptedLastChunk);
589
+
590
+        $this->updateUnencryptedSize($this->getFullPath($path), $newUnencryptedSize);
591
+
592
+        // write to cache if applicable
593
+        $cache = $this->storage->getCache();
594
+        if ($cache) {
595
+            $entry = $cache->get($path);
596
+            $cache->update($entry['fileid'], ['size' => $newUnencryptedSize]);
597
+        }
598
+
599
+        return $newUnencryptedSize;
600
+    }
601
+
602
+    /**
603
+     * @param Storage\IStorage $sourceStorage
604
+     * @param string $sourceInternalPath
605
+     * @param string $targetInternalPath
606
+     * @param bool $preserveMtime
607
+     * @return bool
608
+     */
609
+    public function moveFromStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = true) {
610
+        if ($sourceStorage === $this) {
611
+            return $this->rename($sourceInternalPath, $targetInternalPath);
612
+        }
613
+
614
+        // TODO clean this up once the underlying moveFromStorage in OC\Files\Storage\Wrapper\Common is fixed:
615
+        // - call $this->storage->moveFromStorage() instead of $this->copyBetweenStorage
616
+        // - copy the file cache update from  $this->copyBetweenStorage to this method
617
+        // - copy the copyKeys() call from  $this->copyBetweenStorage to this method
618
+        // - remove $this->copyBetweenStorage
619
+
620
+        if (!$sourceStorage->isDeletable($sourceInternalPath)) {
621
+            return false;
622
+        }
623
+
624
+        $result = $this->copyBetweenStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, true);
625
+        if ($result) {
626
+            if ($sourceStorage->is_dir($sourceInternalPath)) {
627
+                $result &= $sourceStorage->rmdir($sourceInternalPath);
628
+            } else {
629
+                $result &= $sourceStorage->unlink($sourceInternalPath);
630
+            }
631
+        }
632
+        return $result;
633
+    }
634
+
635
+
636
+    /**
637
+     * @param Storage\IStorage $sourceStorage
638
+     * @param string $sourceInternalPath
639
+     * @param string $targetInternalPath
640
+     * @param bool $preserveMtime
641
+     * @param bool $isRename
642
+     * @return bool
643
+     */
644
+    public function copyFromStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false, $isRename = false) {
645
+
646
+        // TODO clean this up once the underlying moveFromStorage in OC\Files\Storage\Wrapper\Common is fixed:
647
+        // - call $this->storage->copyFromStorage() instead of $this->copyBetweenStorage
648
+        // - copy the file cache update from  $this->copyBetweenStorage to this method
649
+        // - copy the copyKeys() call from  $this->copyBetweenStorage to this method
650
+        // - remove $this->copyBetweenStorage
651
+
652
+        return $this->copyBetweenStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename);
653
+    }
654
+
655
+    /**
656
+     * Update the encrypted cache version in the database
657
+     *
658
+     * @param Storage\IStorage $sourceStorage
659
+     * @param string $sourceInternalPath
660
+     * @param string $targetInternalPath
661
+     * @param bool $isRename
662
+     * @param bool $keepEncryptionVersion
663
+     */
664
+    private function updateEncryptedVersion(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, $keepEncryptionVersion) {
665
+        $isEncrypted = $this->encryptionManager->isEnabled() && $this->shouldEncrypt($targetInternalPath);
666
+        $cacheInformation = [
667
+            'encrypted' => $isEncrypted,
668
+        ];
669
+        if($isEncrypted) {
670
+            $encryptedVersion = $sourceStorage->getCache()->get($sourceInternalPath)['encryptedVersion'];
671
+
672
+            // In case of a move operation from an unencrypted to an encrypted
673
+            // storage the old encrypted version would stay with "0" while the
674
+            // correct value would be "1". Thus we manually set the value to "1"
675
+            // for those cases.
676
+            // See also https://github.com/owncloud/core/issues/23078
677
+            if($encryptedVersion === 0 || !$keepEncryptionVersion) {
678
+                $encryptedVersion = 1;
679
+            }
680
+
681
+            $cacheInformation['encryptedVersion'] = $encryptedVersion;
682
+        }
683
+
684
+        // in case of a rename we need to manipulate the source cache because
685
+        // this information will be kept for the new target
686
+        if ($isRename) {
687
+            $sourceStorage->getCache()->put($sourceInternalPath, $cacheInformation);
688
+        } else {
689
+            $this->getCache()->put($targetInternalPath, $cacheInformation);
690
+        }
691
+    }
692
+
693
+    /**
694
+     * copy file between two storages
695
+     *
696
+     * @param Storage\IStorage $sourceStorage
697
+     * @param string $sourceInternalPath
698
+     * @param string $targetInternalPath
699
+     * @param bool $preserveMtime
700
+     * @param bool $isRename
701
+     * @return bool
702
+     * @throws \Exception
703
+     */
704
+    private function copyBetweenStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename) {
705
+
706
+        // for versions we have nothing to do, because versions should always use the
707
+        // key from the original file. Just create a 1:1 copy and done
708
+        if ($this->isVersion($targetInternalPath) ||
709
+            $this->isVersion($sourceInternalPath)) {
710
+            // remember that we try to create a version so that we can detect it during
711
+            // fopen($sourceInternalPath) and by-pass the encryption in order to
712
+            // create a 1:1 copy of the file
713
+            $this->arrayCache->set('encryption_copy_version_' . $sourceInternalPath, true);
714
+            $result = $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
715
+            $this->arrayCache->remove('encryption_copy_version_' . $sourceInternalPath);
716
+            if ($result) {
717
+                $info = $this->getCache('', $sourceStorage)->get($sourceInternalPath);
718
+                // make sure that we update the unencrypted size for the version
719
+                if (isset($info['encrypted']) && $info['encrypted'] === true) {
720
+                    $this->updateUnencryptedSize(
721
+                        $this->getFullPath($targetInternalPath),
722
+                        $info['size']
723
+                    );
724
+                }
725
+                $this->updateEncryptedVersion($sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, true);
726
+            }
727
+            return $result;
728
+        }
729
+
730
+        // first copy the keys that we reuse the existing file key on the target location
731
+        // and don't create a new one which would break versions for example.
732
+        $mount = $this->mountManager->findByStorageId($sourceStorage->getId());
733
+        if (count($mount) === 1) {
734
+            $mountPoint = $mount[0]->getMountPoint();
735
+            $source = $mountPoint . '/' . $sourceInternalPath;
736
+            $target = $this->getFullPath($targetInternalPath);
737
+            $this->copyKeys($source, $target);
738
+        } else {
739
+            $this->logger->error('Could not find mount point, can\'t keep encryption keys');
740
+        }
741
+
742
+        if ($sourceStorage->is_dir($sourceInternalPath)) {
743
+            $dh = $sourceStorage->opendir($sourceInternalPath);
744
+            $result = $this->mkdir($targetInternalPath);
745
+            if (is_resource($dh)) {
746
+                while ($result and ($file = readdir($dh)) !== false) {
747
+                    if (!Filesystem::isIgnoredDir($file)) {
748
+                        $result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file, false, $isRename);
749
+                    }
750
+                }
751
+            }
752
+        } else {
753
+            try {
754
+                $source = $sourceStorage->fopen($sourceInternalPath, 'r');
755
+                $target = $this->fopen($targetInternalPath, 'w');
756
+                list(, $result) = \OC_Helper::streamCopy($source, $target);
757
+                fclose($source);
758
+                fclose($target);
759
+            } catch (\Exception $e) {
760
+                fclose($source);
761
+                fclose($target);
762
+                throw $e;
763
+            }
764
+            if($result) {
765
+                if ($preserveMtime) {
766
+                    $this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath));
767
+                }
768
+                $this->updateEncryptedVersion($sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, false);
769
+            } else {
770
+                // delete partially written target file
771
+                $this->unlink($targetInternalPath);
772
+                // delete cache entry that was created by fopen
773
+                $this->getCache()->remove($targetInternalPath);
774
+            }
775
+        }
776
+        return (bool)$result;
777
+
778
+    }
779
+
780
+    /**
781
+     * get the path to a local version of the file.
782
+     * The local version of the file can be temporary and doesn't have to be persistent across requests
783
+     *
784
+     * @param string $path
785
+     * @return string
786
+     */
787
+    public function getLocalFile($path) {
788
+        if ($this->encryptionManager->isEnabled()) {
789
+            $cachedFile = $this->getCachedFile($path);
790
+            if (is_string($cachedFile)) {
791
+                return $cachedFile;
792
+            }
793
+        }
794
+        return $this->storage->getLocalFile($path);
795
+    }
796
+
797
+    /**
798
+     * Returns the wrapped storage's value for isLocal()
799
+     *
800
+     * @return bool wrapped storage's isLocal() value
801
+     */
802
+    public function isLocal() {
803
+        if ($this->encryptionManager->isEnabled()) {
804
+            return false;
805
+        }
806
+        return $this->storage->isLocal();
807
+    }
808
+
809
+    /**
810
+     * see http://php.net/manual/en/function.stat.php
811
+     * only the following keys are required in the result: size and mtime
812
+     *
813
+     * @param string $path
814
+     * @return array
815
+     */
816
+    public function stat($path) {
817
+        $stat = $this->storage->stat($path);
818
+        $fileSize = $this->filesize($path);
819
+        $stat['size'] = $fileSize;
820
+        $stat[7] = $fileSize;
821
+        return $stat;
822
+    }
823
+
824
+    /**
825
+     * see http://php.net/manual/en/function.hash.php
826
+     *
827
+     * @param string $type
828
+     * @param string $path
829
+     * @param bool $raw
830
+     * @return string
831
+     */
832
+    public function hash($type, $path, $raw = false) {
833
+        $fh = $this->fopen($path, 'rb');
834
+        $ctx = hash_init($type);
835
+        hash_update_stream($ctx, $fh);
836
+        fclose($fh);
837
+        return hash_final($ctx, $raw);
838
+    }
839
+
840
+    /**
841
+     * return full path, including mount point
842
+     *
843
+     * @param string $path relative to mount point
844
+     * @return string full path including mount point
845
+     */
846
+    protected function getFullPath($path) {
847
+        return Filesystem::normalizePath($this->mountPoint . '/' . $path);
848
+    }
849
+
850
+    /**
851
+     * read first block of encrypted file, typically this will contain the
852
+     * encryption header
853
+     *
854
+     * @param string $path
855
+     * @return string
856
+     */
857
+    protected function readFirstBlock($path) {
858
+        $firstBlock = '';
859
+        if ($this->storage->file_exists($path)) {
860
+            $handle = $this->storage->fopen($path, 'r');
861
+            $firstBlock = fread($handle, $this->util->getHeaderSize());
862
+            fclose($handle);
863
+        }
864
+        return $firstBlock;
865
+    }
866
+
867
+    /**
868
+     * return header size of given file
869
+     *
870
+     * @param string $path
871
+     * @return int
872
+     */
873
+    protected function getHeaderSize($path) {
874
+        $headerSize = 0;
875
+        $realFile = $this->util->stripPartialFileExtension($path);
876
+        if ($this->storage->file_exists($realFile)) {
877
+            $path = $realFile;
878
+        }
879
+        $firstBlock = $this->readFirstBlock($path);
880
+
881
+        if (substr($firstBlock, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) {
882
+            $headerSize = $this->util->getHeaderSize();
883
+        }
884
+
885
+        return $headerSize;
886
+    }
887
+
888
+    /**
889
+     * parse raw header to array
890
+     *
891
+     * @param string $rawHeader
892
+     * @return array
893
+     */
894
+    protected function parseRawHeader($rawHeader) {
895
+        $result = [];
896
+        if (substr($rawHeader, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) {
897
+            $header = $rawHeader;
898
+            $endAt = strpos($header, Util::HEADER_END);
899
+            if ($endAt !== false) {
900
+                $header = substr($header, 0, $endAt + strlen(Util::HEADER_END));
901
+
902
+                // +1 to not start with an ':' which would result in empty element at the beginning
903
+                $exploded = explode(':', substr($header, strlen(Util::HEADER_START)+1));
904
+
905
+                $element = array_shift($exploded);
906
+                while ($element !== Util::HEADER_END) {
907
+                    $result[$element] = array_shift($exploded);
908
+                    $element = array_shift($exploded);
909
+                }
910
+            }
911
+        }
912
+
913
+        return $result;
914
+    }
915
+
916
+    /**
917
+     * read header from file
918
+     *
919
+     * @param string $path
920
+     * @return array
921
+     */
922
+    protected function getHeader($path) {
923
+        $realFile = $this->util->stripPartialFileExtension($path);
924
+        $exists = $this->storage->file_exists($realFile);
925
+        if ($exists) {
926
+            $path = $realFile;
927
+        }
928
+
929
+        $firstBlock = $this->readFirstBlock($path);
930
+        $result = $this->parseRawHeader($firstBlock);
931
+
932
+        // if the header doesn't contain a encryption module we check if it is a
933
+        // legacy file. If true, we add the default encryption module
934
+        if (!isset($result[Util::HEADER_ENCRYPTION_MODULE_KEY])) {
935
+            if (!empty($result)) {
936
+                $result[Util::HEADER_ENCRYPTION_MODULE_KEY] = 'OC_DEFAULT_MODULE';
937
+            } else if ($exists) {
938
+                // if the header was empty we have to check first if it is a encrypted file at all
939
+                // We would do query to filecache only if we know that entry in filecache exists
940
+                $info = $this->getCache()->get($path);
941
+                if (isset($info['encrypted']) && $info['encrypted'] === true) {
942
+                    $result[Util::HEADER_ENCRYPTION_MODULE_KEY] = 'OC_DEFAULT_MODULE';
943
+                }
944
+            }
945
+        }
946
+
947
+        return $result;
948
+    }
949
+
950
+    /**
951
+     * read encryption module needed to read/write the file located at $path
952
+     *
953
+     * @param string $path
954
+     * @return null|\OCP\Encryption\IEncryptionModule
955
+     * @throws ModuleDoesNotExistsException
956
+     * @throws \Exception
957
+     */
958
+    protected function getEncryptionModule($path) {
959
+        $encryptionModule = null;
960
+        $header = $this->getHeader($path);
961
+        $encryptionModuleId = $this->util->getEncryptionModuleId($header);
962
+        if (!empty($encryptionModuleId)) {
963
+            try {
964
+                $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
965
+            } catch (ModuleDoesNotExistsException $e) {
966
+                $this->logger->critical('Encryption module defined in "' . $path . '" not loaded!');
967
+                throw $e;
968
+            }
969
+        }
970
+
971
+        return $encryptionModule;
972
+    }
973
+
974
+    /**
975
+     * @param string $path
976
+     * @param int $unencryptedSize
977
+     */
978
+    public function updateUnencryptedSize($path, $unencryptedSize) {
979
+        $this->unencryptedSize[$path] = $unencryptedSize;
980
+    }
981
+
982
+    /**
983
+     * copy keys to new location
984
+     *
985
+     * @param string $source path relative to data/
986
+     * @param string $target path relative to data/
987
+     * @return bool
988
+     */
989
+    protected function copyKeys($source, $target) {
990
+        if (!$this->util->isExcluded($source)) {
991
+            return $this->keyStorage->copyKeys($source, $target);
992
+        }
993
+
994
+        return false;
995
+    }
996
+
997
+    /**
998
+     * check if path points to a files version
999
+     *
1000
+     * @param $path
1001
+     * @return bool
1002
+     */
1003
+    protected function isVersion($path) {
1004
+        $normalized = Filesystem::normalizePath($path);
1005
+        return substr($normalized, 0, strlen('/files_versions/')) === '/files_versions/';
1006
+    }
1007
+
1008
+    /**
1009
+     * check if the given storage should be encrypted or not
1010
+     *
1011
+     * @param $path
1012
+     * @return bool
1013
+     */
1014
+    protected function shouldEncrypt($path) {
1015
+        $fullPath = $this->getFullPath($path);
1016
+        $mountPointConfig = $this->mount->getOption('encrypt', true);
1017
+        if ($mountPointConfig === false) {
1018
+            return false;
1019
+        }
1020
+
1021
+        try {
1022
+            $encryptionModule = $this->getEncryptionModule($fullPath);
1023
+        } catch (ModuleDoesNotExistsException $e) {
1024
+            return false;
1025
+        }
1026
+
1027
+        if ($encryptionModule === null) {
1028
+            $encryptionModule = $this->encryptionManager->getEncryptionModule();
1029
+        }
1030
+
1031
+        return $encryptionModule->shouldEncrypt($fullPath);
1032
+
1033
+    }
1034
+
1035
+    public function writeStream(string $path, $stream, int $size = null): int {
1036
+        // always fall back to fopen
1037
+        $target = $this->fopen($path, 'w');
1038
+        list($count, $result) = \OC_Helper::streamCopy($stream, $target);
1039
+        fclose($target);
1040
+        return $count;
1041
+    }
1042 1042
 
1043 1043
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/DAV.php 1 patch
Indentation   +809 added lines, -809 removed lines patch added patch discarded remove patch
@@ -62,813 +62,813 @@
 block discarded – undo
62 62
  * @package OC\Files\Storage
63 63
  */
64 64
 class DAV extends Common {
65
-	/** @var string */
66
-	protected $password;
67
-	/** @var string */
68
-	protected $user;
69
-	/** @var string */
70
-	protected $authType;
71
-	/** @var string */
72
-	protected $host;
73
-	/** @var bool */
74
-	protected $secure;
75
-	/** @var string */
76
-	protected $root;
77
-	/** @var string */
78
-	protected $certPath;
79
-	/** @var bool */
80
-	protected $ready;
81
-	/** @var Client */
82
-	protected $client;
83
-	/** @var ArrayCache */
84
-	protected $statCache;
85
-	/** @var IClientService */
86
-	protected $httpClientService;
87
-	/** @var ICertificateManager */
88
-	protected $certManager;
89
-
90
-	/**
91
-	 * @param array $params
92
-	 * @throws \Exception
93
-	 */
94
-	public function __construct($params) {
95
-		$this->statCache = new ArrayCache();
96
-		$this->httpClientService = \OC::$server->getHTTPClientService();
97
-		if (isset($params['host']) && isset($params['user']) && isset($params['password'])) {
98
-			$host = $params['host'];
99
-			//remove leading http[s], will be generated in createBaseUri()
100
-			if (substr($host, 0, 8) == "https://") $host = substr($host, 8);
101
-			else if (substr($host, 0, 7) == "http://") $host = substr($host, 7);
102
-			$this->host = $host;
103
-			$this->user = $params['user'];
104
-			$this->password = $params['password'];
105
-			if (isset($params['authType'])) {
106
-				$this->authType = $params['authType'];
107
-			}
108
-			if (isset($params['secure'])) {
109
-				if (is_string($params['secure'])) {
110
-					$this->secure = ($params['secure'] === 'true');
111
-				} else {
112
-					$this->secure = (bool)$params['secure'];
113
-				}
114
-			} else {
115
-				$this->secure = false;
116
-			}
117
-			if ($this->secure === true) {
118
-				// inject mock for testing
119
-				$this->certManager = \OC::$server->getCertificateManager();
120
-				if (is_null($this->certManager)) { //no user
121
-					$this->certManager = \OC::$server->getCertificateManager(null);
122
-				}
123
-			}
124
-			$this->root = $params['root'] ?? '/';
125
-			$this->root = '/' . ltrim($this->root, '/');
126
-			$this->root = rtrim($this->root, '/') . '/';
127
-		} else {
128
-			throw new \Exception('Invalid webdav storage configuration');
129
-		}
130
-	}
131
-
132
-	protected function init() {
133
-		if ($this->ready) {
134
-			return;
135
-		}
136
-		$this->ready = true;
137
-
138
-		$settings = [
139
-			'baseUri' => $this->createBaseUri(),
140
-			'userName' => $this->user,
141
-			'password' => $this->password,
142
-		];
143
-		if (isset($this->authType)) {
144
-			$settings['authType'] = $this->authType;
145
-		}
146
-
147
-		$proxy = \OC::$server->getConfig()->getSystemValue('proxy', '');
148
-		if ($proxy !== '') {
149
-			$settings['proxy'] = $proxy;
150
-		}
151
-
152
-		$this->client = new Client($settings);
153
-		$this->client->setThrowExceptions(true);
154
-
155
-		if($this->secure === true) {
156
-			$certPath = $this->certManager->getAbsoluteBundlePath();
157
-			if (file_exists($certPath)) {
158
-				$this->certPath = $certPath;
159
-			}
160
-			if ($this->certPath) {
161
-				$this->client->addCurlSetting(CURLOPT_CAINFO, $this->certPath);
162
-			}
163
-		}
164
-	}
165
-
166
-	/**
167
-	 * Clear the stat cache
168
-	 */
169
-	public function clearStatCache() {
170
-		$this->statCache->clear();
171
-	}
172
-
173
-	/** {@inheritdoc} */
174
-	public function getId() {
175
-		return 'webdav::' . $this->user . '@' . $this->host . '/' . $this->root;
176
-	}
177
-
178
-	/** {@inheritdoc} */
179
-	public function createBaseUri() {
180
-		$baseUri = 'http';
181
-		if ($this->secure) {
182
-			$baseUri .= 's';
183
-		}
184
-		$baseUri .= '://' . $this->host . $this->root;
185
-		return $baseUri;
186
-	}
187
-
188
-	/** {@inheritdoc} */
189
-	public function mkdir($path) {
190
-		$this->init();
191
-		$path = $this->cleanPath($path);
192
-		$result = $this->simpleResponse('MKCOL', $path, null, 201);
193
-		if ($result) {
194
-			$this->statCache->set($path, true);
195
-		}
196
-		return $result;
197
-	}
198
-
199
-	/** {@inheritdoc} */
200
-	public function rmdir($path) {
201
-		$this->init();
202
-		$path = $this->cleanPath($path);
203
-		// FIXME: some WebDAV impl return 403 when trying to DELETE
204
-		// a non-empty folder
205
-		$result = $this->simpleResponse('DELETE', $path . '/', null, 204);
206
-		$this->statCache->clear($path . '/');
207
-		$this->statCache->remove($path);
208
-		return $result;
209
-	}
210
-
211
-	/** {@inheritdoc} */
212
-	public function opendir($path) {
213
-		$this->init();
214
-		$path = $this->cleanPath($path);
215
-		try {
216
-			$response = $this->client->propFind(
217
-				$this->encodePath($path),
218
-				['{DAV:}getetag'],
219
-				1
220
-			);
221
-			if ($response === false) {
222
-				return false;
223
-			}
224
-			$content = [];
225
-			$files = array_keys($response);
226
-			array_shift($files); //the first entry is the current directory
227
-
228
-			if (!$this->statCache->hasKey($path)) {
229
-				$this->statCache->set($path, true);
230
-			}
231
-			foreach ($files as $file) {
232
-				$file = urldecode($file);
233
-				// do not store the real entry, we might not have all properties
234
-				if (!$this->statCache->hasKey($path)) {
235
-					$this->statCache->set($file, true);
236
-				}
237
-				$file = basename($file);
238
-				$content[] = $file;
239
-			}
240
-			return IteratorDirectory::wrap($content);
241
-		} catch (\Exception $e) {
242
-			$this->convertException($e, $path);
243
-		}
244
-		return false;
245
-	}
246
-
247
-	/**
248
-	 * Propfind call with cache handling.
249
-	 *
250
-	 * First checks if information is cached.
251
-	 * If not, request it from the server then store to cache.
252
-	 *
253
-	 * @param string $path path to propfind
254
-	 *
255
-	 * @return array|boolean propfind response or false if the entry was not found
256
-	 *
257
-	 * @throws ClientHttpException
258
-	 */
259
-	protected function propfind($path) {
260
-		$path = $this->cleanPath($path);
261
-		$cachedResponse = $this->statCache->get($path);
262
-		// we either don't know it, or we know it exists but need more details
263
-		if (is_null($cachedResponse) || $cachedResponse === true) {
264
-			$this->init();
265
-			try {
266
-				$response = $this->client->propFind(
267
-					$this->encodePath($path),
268
-					[
269
-						'{DAV:}getlastmodified',
270
-						'{DAV:}getcontentlength',
271
-						'{DAV:}getcontenttype',
272
-						'{http://owncloud.org/ns}permissions',
273
-						'{http://open-collaboration-services.org/ns}share-permissions',
274
-						'{DAV:}resourcetype',
275
-						'{DAV:}getetag',
276
-					]
277
-				);
278
-				$this->statCache->set($path, $response);
279
-			} catch (ClientHttpException $e) {
280
-				if ($e->getHttpStatus() === 404 || $e->getHttpStatus() === 405) {
281
-					$this->statCache->clear($path . '/');
282
-					$this->statCache->set($path, false);
283
-					return false;
284
-				}
285
-				$this->convertException($e, $path);
286
-			} catch (\Exception $e) {
287
-				$this->convertException($e, $path);
288
-			}
289
-		} else {
290
-			$response = $cachedResponse;
291
-		}
292
-		return $response;
293
-	}
294
-
295
-	/** {@inheritdoc} */
296
-	public function filetype($path) {
297
-		try {
298
-			$response = $this->propfind($path);
299
-			if ($response === false) {
300
-				return false;
301
-			}
302
-			$responseType = [];
303
-			if (isset($response["{DAV:}resourcetype"])) {
304
-				/** @var ResourceType[] $response */
305
-				$responseType = $response["{DAV:}resourcetype"]->getValue();
306
-			}
307
-			return (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file';
308
-		} catch (\Exception $e) {
309
-			$this->convertException($e, $path);
310
-		}
311
-		return false;
312
-	}
313
-
314
-	/** {@inheritdoc} */
315
-	public function file_exists($path) {
316
-		try {
317
-			$path = $this->cleanPath($path);
318
-			$cachedState = $this->statCache->get($path);
319
-			if ($cachedState === false) {
320
-				// we know the file doesn't exist
321
-				return false;
322
-			} else if (!is_null($cachedState)) {
323
-				return true;
324
-			}
325
-			// need to get from server
326
-			return ($this->propfind($path) !== false);
327
-		} catch (\Exception $e) {
328
-			$this->convertException($e, $path);
329
-		}
330
-		return false;
331
-	}
332
-
333
-	/** {@inheritdoc} */
334
-	public function unlink($path) {
335
-		$this->init();
336
-		$path = $this->cleanPath($path);
337
-		$result = $this->simpleResponse('DELETE', $path, null, 204);
338
-		$this->statCache->clear($path . '/');
339
-		$this->statCache->remove($path);
340
-		return $result;
341
-	}
342
-
343
-	/** {@inheritdoc} */
344
-	public function fopen($path, $mode) {
345
-		$this->init();
346
-		$path = $this->cleanPath($path);
347
-		switch ($mode) {
348
-			case 'r':
349
-			case 'rb':
350
-				try {
351
-					$response = $this->httpClientService
352
-						->newClient()
353
-						->get($this->createBaseUri() . $this->encodePath($path), [
354
-							'auth' => [$this->user, $this->password],
355
-							'stream' => true
356
-						]);
357
-				} catch (\GuzzleHttp\Exception\ClientException $e) {
358
-					if ($e->getResponse() instanceof ResponseInterface
359
-						&& $e->getResponse()->getStatusCode() === 404) {
360
-						return false;
361
-					} else {
362
-						throw $e;
363
-					}
364
-				}
365
-
366
-				if ($response->getStatusCode() !== Http::STATUS_OK) {
367
-					if ($response->getStatusCode() === Http::STATUS_LOCKED) {
368
-						throw new \OCP\Lock\LockedException($path);
369
-					} else {
370
-						Util::writeLog("webdav client", 'Guzzle get returned status code ' . $response->getStatusCode(), ILogger::ERROR);
371
-					}
372
-				}
373
-
374
-				return $response->getBody();
375
-			case 'w':
376
-			case 'wb':
377
-			case 'a':
378
-			case 'ab':
379
-			case 'r+':
380
-			case 'w+':
381
-			case 'wb+':
382
-			case 'a+':
383
-			case 'x':
384
-			case 'x+':
385
-			case 'c':
386
-			case 'c+':
387
-				//emulate these
388
-				$tempManager = \OC::$server->getTempManager();
389
-				if (strrpos($path, '.') !== false) {
390
-					$ext = substr($path, strrpos($path, '.'));
391
-				} else {
392
-					$ext = '';
393
-				}
394
-				if ($this->file_exists($path)) {
395
-					if (!$this->isUpdatable($path)) {
396
-						return false;
397
-					}
398
-					if ($mode === 'w' or $mode === 'w+') {
399
-						$tmpFile = $tempManager->getTemporaryFile($ext);
400
-					} else {
401
-						$tmpFile = $this->getCachedFile($path);
402
-					}
403
-				} else {
404
-					if (!$this->isCreatable(dirname($path))) {
405
-						return false;
406
-					}
407
-					$tmpFile = $tempManager->getTemporaryFile($ext);
408
-				}
409
-				$handle = fopen($tmpFile, $mode);
410
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
411
-					$this->writeBack($tmpFile, $path);
412
-				});
413
-		}
414
-	}
415
-
416
-	/**
417
-	 * @param string $tmpFile
418
-	 */
419
-	public function writeBack($tmpFile, $path) {
420
-		$this->uploadFile($tmpFile, $path);
421
-		unlink($tmpFile);
422
-	}
423
-
424
-	/** {@inheritdoc} */
425
-	public function free_space($path) {
426
-		$this->init();
427
-		$path = $this->cleanPath($path);
428
-		try {
429
-			// TODO: cacheable ?
430
-			$response = $this->client->propfind($this->encodePath($path), ['{DAV:}quota-available-bytes']);
431
-			if ($response === false) {
432
-				return FileInfo::SPACE_UNKNOWN;
433
-			}
434
-			if (isset($response['{DAV:}quota-available-bytes'])) {
435
-				return (int)$response['{DAV:}quota-available-bytes'];
436
-			} else {
437
-				return FileInfo::SPACE_UNKNOWN;
438
-			}
439
-		} catch (\Exception $e) {
440
-			return FileInfo::SPACE_UNKNOWN;
441
-		}
442
-	}
443
-
444
-	/** {@inheritdoc} */
445
-	public function touch($path, $mtime = null) {
446
-		$this->init();
447
-		if (is_null($mtime)) {
448
-			$mtime = time();
449
-		}
450
-		$path = $this->cleanPath($path);
451
-
452
-		// if file exists, update the mtime, else create a new empty file
453
-		if ($this->file_exists($path)) {
454
-			try {
455
-				$this->statCache->remove($path);
456
-				$this->client->proppatch($this->encodePath($path), ['{DAV:}lastmodified' => $mtime]);
457
-				// non-owncloud clients might not have accepted the property, need to recheck it
458
-				$response = $this->client->propfind($this->encodePath($path), ['{DAV:}getlastmodified'], 0);
459
-				if ($response === false) {
460
-					return false;
461
-				}
462
-				if (isset($response['{DAV:}getlastmodified'])) {
463
-					$remoteMtime = strtotime($response['{DAV:}getlastmodified']);
464
-					if ($remoteMtime !== $mtime) {
465
-						// server has not accepted the mtime
466
-						return false;
467
-					}
468
-				}
469
-			} catch (ClientHttpException $e) {
470
-				if ($e->getHttpStatus() === 501) {
471
-					return false;
472
-				}
473
-				$this->convertException($e, $path);
474
-				return false;
475
-			} catch (\Exception $e) {
476
-				$this->convertException($e, $path);
477
-				return false;
478
-			}
479
-		} else {
480
-			$this->file_put_contents($path, '');
481
-		}
482
-		return true;
483
-	}
484
-
485
-	/**
486
-	 * @param string $path
487
-	 * @param string $data
488
-	 * @return int
489
-	 */
490
-	public function file_put_contents($path, $data) {
491
-		$path = $this->cleanPath($path);
492
-		$result = parent::file_put_contents($path, $data);
493
-		$this->statCache->remove($path);
494
-		return $result;
495
-	}
496
-
497
-	/**
498
-	 * @param string $path
499
-	 * @param string $target
500
-	 */
501
-	protected function uploadFile($path, $target) {
502
-		$this->init();
503
-
504
-		// invalidate
505
-		$target = $this->cleanPath($target);
506
-		$this->statCache->remove($target);
507
-		$source = fopen($path, 'r');
508
-
509
-		$this->httpClientService
510
-			->newClient()
511
-			->put($this->createBaseUri() . $this->encodePath($target), [
512
-				'body' => $source,
513
-				'auth' => [$this->user, $this->password]
514
-			]);
515
-
516
-		$this->removeCachedFile($target);
517
-	}
518
-
519
-	/** {@inheritdoc} */
520
-	public function rename($path1, $path2) {
521
-		$this->init();
522
-		$path1 = $this->cleanPath($path1);
523
-		$path2 = $this->cleanPath($path2);
524
-		try {
525
-			// overwrite directory ?
526
-			if ($this->is_dir($path2)) {
527
-				// needs trailing slash in destination
528
-				$path2 = rtrim($path2, '/') . '/';
529
-			}
530
-			$this->client->request(
531
-				'MOVE',
532
-				$this->encodePath($path1),
533
-				null,
534
-				[
535
-					'Destination' => $this->createBaseUri() . $this->encodePath($path2),
536
-				]
537
-			);
538
-			$this->statCache->clear($path1 . '/');
539
-			$this->statCache->clear($path2 . '/');
540
-			$this->statCache->set($path1, false);
541
-			$this->statCache->set($path2, true);
542
-			$this->removeCachedFile($path1);
543
-			$this->removeCachedFile($path2);
544
-			return true;
545
-		} catch (\Exception $e) {
546
-			$this->convertException($e);
547
-		}
548
-		return false;
549
-	}
550
-
551
-	/** {@inheritdoc} */
552
-	public function copy($path1, $path2) {
553
-		$this->init();
554
-		$path1 = $this->cleanPath($path1);
555
-		$path2 = $this->cleanPath($path2);
556
-		try {
557
-			// overwrite directory ?
558
-			if ($this->is_dir($path2)) {
559
-				// needs trailing slash in destination
560
-				$path2 = rtrim($path2, '/') . '/';
561
-			}
562
-			$this->client->request(
563
-				'COPY',
564
-				$this->encodePath($path1),
565
-				null,
566
-				[
567
-					'Destination' => $this->createBaseUri() . $this->encodePath($path2),
568
-				]
569
-			);
570
-			$this->statCache->clear($path2 . '/');
571
-			$this->statCache->set($path2, true);
572
-			$this->removeCachedFile($path2);
573
-			return true;
574
-		} catch (\Exception $e) {
575
-			$this->convertException($e);
576
-		}
577
-		return false;
578
-	}
579
-
580
-	/** {@inheritdoc} */
581
-	public function stat($path) {
582
-		try {
583
-			$response = $this->propfind($path);
584
-			if (!$response) {
585
-				return false;
586
-			}
587
-			return [
588
-				'mtime' => strtotime($response['{DAV:}getlastmodified']),
589
-				'size' => (int)isset($response['{DAV:}getcontentlength']) ? $response['{DAV:}getcontentlength'] : 0,
590
-			];
591
-		} catch (\Exception $e) {
592
-			$this->convertException($e, $path);
593
-		}
594
-		return [];
595
-	}
596
-
597
-	/** {@inheritdoc} */
598
-	public function getMimeType($path) {
599
-		$remoteMimetype = $this->getMimeTypeFromRemote($path);
600
-		if ($remoteMimetype === 'application/octet-stream') {
601
-			return \OC::$server->getMimeTypeDetector()->detectPath($path);
602
-		} else {
603
-			return $remoteMimetype;
604
-		}
605
-	}
606
-
607
-	public function getMimeTypeFromRemote($path) {
608
-		try {
609
-			$response = $this->propfind($path);
610
-			if ($response === false) {
611
-				return false;
612
-			}
613
-			$responseType = [];
614
-			if (isset($response["{DAV:}resourcetype"])) {
615
-				/** @var ResourceType[] $response */
616
-				$responseType = $response["{DAV:}resourcetype"]->getValue();
617
-			}
618
-			$type = (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file';
619
-			if ($type == 'dir') {
620
-				return 'httpd/unix-directory';
621
-			} elseif (isset($response['{DAV:}getcontenttype'])) {
622
-				return $response['{DAV:}getcontenttype'];
623
-			} else {
624
-				return 'application/octet-stream';
625
-			}
626
-		} catch (\Exception $e) {
627
-			return false;
628
-		}
629
-	}
630
-
631
-	/**
632
-	 * @param string $path
633
-	 * @return string
634
-	 */
635
-	public function cleanPath($path) {
636
-		if ($path === '') {
637
-			return $path;
638
-		}
639
-		$path = Filesystem::normalizePath($path);
640
-		// remove leading slash
641
-		return substr($path, 1);
642
-	}
643
-
644
-	/**
645
-	 * URL encodes the given path but keeps the slashes
646
-	 *
647
-	 * @param string $path to encode
648
-	 * @return string encoded path
649
-	 */
650
-	protected function encodePath($path) {
651
-		// slashes need to stay
652
-		return str_replace('%2F', '/', rawurlencode($path));
653
-	}
654
-
655
-	/**
656
-	 * @param string $method
657
-	 * @param string $path
658
-	 * @param string|resource|null $body
659
-	 * @param int $expected
660
-	 * @return bool
661
-	 * @throws StorageInvalidException
662
-	 * @throws StorageNotAvailableException
663
-	 */
664
-	protected function simpleResponse($method, $path, $body, $expected) {
665
-		$path = $this->cleanPath($path);
666
-		try {
667
-			$response = $this->client->request($method, $this->encodePath($path), $body);
668
-			return $response['statusCode'] == $expected;
669
-		} catch (ClientHttpException $e) {
670
-			if ($e->getHttpStatus() === 404 && $method === 'DELETE') {
671
-				$this->statCache->clear($path . '/');
672
-				$this->statCache->set($path, false);
673
-				return false;
674
-			}
675
-
676
-			$this->convertException($e, $path);
677
-		} catch (\Exception $e) {
678
-			$this->convertException($e, $path);
679
-		}
680
-		return false;
681
-	}
682
-
683
-	/**
684
-	 * check if curl is installed
685
-	 */
686
-	public static function checkDependencies() {
687
-		return true;
688
-	}
689
-
690
-	/** {@inheritdoc} */
691
-	public function isUpdatable($path) {
692
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_UPDATE);
693
-	}
694
-
695
-	/** {@inheritdoc} */
696
-	public function isCreatable($path) {
697
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_CREATE);
698
-	}
699
-
700
-	/** {@inheritdoc} */
701
-	public function isSharable($path) {
702
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE);
703
-	}
704
-
705
-	/** {@inheritdoc} */
706
-	public function isDeletable($path) {
707
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_DELETE);
708
-	}
709
-
710
-	/** {@inheritdoc} */
711
-	public function getPermissions($path) {
712
-		$this->init();
713
-		$path = $this->cleanPath($path);
714
-		$response = $this->propfind($path);
715
-		if ($response === false) {
716
-			return 0;
717
-		}
718
-		if (isset($response['{http://owncloud.org/ns}permissions'])) {
719
-			return $this->parsePermissions($response['{http://owncloud.org/ns}permissions']);
720
-		} else if ($this->is_dir($path)) {
721
-			return Constants::PERMISSION_ALL;
722
-		} else if ($this->file_exists($path)) {
723
-			return Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE;
724
-		} else {
725
-			return 0;
726
-		}
727
-	}
728
-
729
-	/** {@inheritdoc} */
730
-	public function getETag($path) {
731
-		$this->init();
732
-		$path = $this->cleanPath($path);
733
-		$response = $this->propfind($path);
734
-		if ($response === false) {
735
-			return null;
736
-		}
737
-		if (isset($response['{DAV:}getetag'])) {
738
-			$etag = trim($response['{DAV:}getetag'], '"');
739
-			if (strlen($etag) > 40) {
740
-				$etag = md5($etag);
741
-			}
742
-			return $etag;
743
-		}
744
-		return parent::getEtag($path);
745
-	}
746
-
747
-	/**
748
-	 * @param string $permissionsString
749
-	 * @return int
750
-	 */
751
-	protected function parsePermissions($permissionsString) {
752
-		$permissions = Constants::PERMISSION_READ;
753
-		if (strpos($permissionsString, 'R') !== false) {
754
-			$permissions |= Constants::PERMISSION_SHARE;
755
-		}
756
-		if (strpos($permissionsString, 'D') !== false) {
757
-			$permissions |= Constants::PERMISSION_DELETE;
758
-		}
759
-		if (strpos($permissionsString, 'W') !== false) {
760
-			$permissions |= Constants::PERMISSION_UPDATE;
761
-		}
762
-		if (strpos($permissionsString, 'CK') !== false) {
763
-			$permissions |= Constants::PERMISSION_CREATE;
764
-			$permissions |= Constants::PERMISSION_UPDATE;
765
-		}
766
-		return $permissions;
767
-	}
768
-
769
-	/**
770
-	 * check if a file or folder has been updated since $time
771
-	 *
772
-	 * @param string $path
773
-	 * @param int $time
774
-	 * @throws \OCP\Files\StorageNotAvailableException
775
-	 * @return bool
776
-	 */
777
-	public function hasUpdated($path, $time) {
778
-		$this->init();
779
-		$path = $this->cleanPath($path);
780
-		try {
781
-			// force refresh for $path
782
-			$this->statCache->remove($path);
783
-			$response = $this->propfind($path);
784
-			if ($response === false) {
785
-				if ($path === '') {
786
-					// if root is gone it means the storage is not available
787
-					throw new StorageNotAvailableException('root is gone');
788
-				}
789
-				return false;
790
-			}
791
-			if (isset($response['{DAV:}getetag'])) {
792
-				$cachedData = $this->getCache()->get($path);
793
-				$etag = null;
794
-				if (isset($response['{DAV:}getetag'])) {
795
-					$etag = trim($response['{DAV:}getetag'], '"');
796
-				}
797
-				if (!empty($etag) && $cachedData['etag'] !== $etag) {
798
-					return true;
799
-				} else if (isset($response['{http://open-collaboration-services.org/ns}share-permissions'])) {
800
-					$sharePermissions = (int)$response['{http://open-collaboration-services.org/ns}share-permissions'];
801
-					return $sharePermissions !== $cachedData['permissions'];
802
-				} else if (isset($response['{http://owncloud.org/ns}permissions'])) {
803
-					$permissions = $this->parsePermissions($response['{http://owncloud.org/ns}permissions']);
804
-					return $permissions !== $cachedData['permissions'];
805
-				} else {
806
-					return false;
807
-				}
808
-			} else {
809
-				$remoteMtime = strtotime($response['{DAV:}getlastmodified']);
810
-				return $remoteMtime > $time;
811
-			}
812
-		} catch (ClientHttpException $e) {
813
-			if ($e->getHttpStatus() === 405) {
814
-				if ($path === '') {
815
-					// if root is gone it means the storage is not available
816
-					throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
817
-				}
818
-				return false;
819
-			}
820
-			$this->convertException($e, $path);
821
-			return false;
822
-		} catch (\Exception $e) {
823
-			$this->convertException($e, $path);
824
-			return false;
825
-		}
826
-	}
827
-
828
-	/**
829
-	 * Interpret the given exception and decide whether it is due to an
830
-	 * unavailable storage, invalid storage or other.
831
-	 * This will either throw StorageInvalidException, StorageNotAvailableException
832
-	 * or do nothing.
833
-	 *
834
-	 * @param Exception $e sabre exception
835
-	 * @param string $path optional path from the operation
836
-	 *
837
-	 * @throws StorageInvalidException if the storage is invalid, for example
838
-	 * when the authentication expired or is invalid
839
-	 * @throws StorageNotAvailableException if the storage is not available,
840
-	 * which might be temporary
841
-	 * @throws ForbiddenException if the action is not allowed
842
-	 */
843
-	protected function convertException(Exception $e, $path = '') {
844
-		\OC::$server->getLogger()->logException($e, ['app' => 'files_external', 'level' => ILogger::DEBUG]);
845
-		if ($e instanceof ClientHttpException) {
846
-			if ($e->getHttpStatus() === Http::STATUS_LOCKED) {
847
-				throw new \OCP\Lock\LockedException($path);
848
-			}
849
-			if ($e->getHttpStatus() === Http::STATUS_UNAUTHORIZED) {
850
-				// either password was changed or was invalid all along
851
-				throw new StorageInvalidException(get_class($e) . ': ' . $e->getMessage());
852
-			} else if ($e->getHttpStatus() === Http::STATUS_METHOD_NOT_ALLOWED) {
853
-				// ignore exception for MethodNotAllowed, false will be returned
854
-				return;
855
-			} else if ($e->getHttpStatus() === Http::STATUS_FORBIDDEN){
856
-				// The operation is forbidden. Fail somewhat gracefully
857
-				throw new ForbiddenException(get_class($e) . ':' . $e->getMessage());
858
-			}
859
-			throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
860
-		} else if ($e instanceof ClientException) {
861
-			// connection timeout or refused, server could be temporarily down
862
-			throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
863
-		} else if ($e instanceof \InvalidArgumentException) {
864
-			// parse error because the server returned HTML instead of XML,
865
-			// possibly temporarily down
866
-			throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
867
-		} else if (($e instanceof StorageNotAvailableException) || ($e instanceof StorageInvalidException)) {
868
-			// rethrow
869
-			throw $e;
870
-		}
871
-
872
-		// TODO: only log for now, but in the future need to wrap/rethrow exception
873
-	}
65
+    /** @var string */
66
+    protected $password;
67
+    /** @var string */
68
+    protected $user;
69
+    /** @var string */
70
+    protected $authType;
71
+    /** @var string */
72
+    protected $host;
73
+    /** @var bool */
74
+    protected $secure;
75
+    /** @var string */
76
+    protected $root;
77
+    /** @var string */
78
+    protected $certPath;
79
+    /** @var bool */
80
+    protected $ready;
81
+    /** @var Client */
82
+    protected $client;
83
+    /** @var ArrayCache */
84
+    protected $statCache;
85
+    /** @var IClientService */
86
+    protected $httpClientService;
87
+    /** @var ICertificateManager */
88
+    protected $certManager;
89
+
90
+    /**
91
+     * @param array $params
92
+     * @throws \Exception
93
+     */
94
+    public function __construct($params) {
95
+        $this->statCache = new ArrayCache();
96
+        $this->httpClientService = \OC::$server->getHTTPClientService();
97
+        if (isset($params['host']) && isset($params['user']) && isset($params['password'])) {
98
+            $host = $params['host'];
99
+            //remove leading http[s], will be generated in createBaseUri()
100
+            if (substr($host, 0, 8) == "https://") $host = substr($host, 8);
101
+            else if (substr($host, 0, 7) == "http://") $host = substr($host, 7);
102
+            $this->host = $host;
103
+            $this->user = $params['user'];
104
+            $this->password = $params['password'];
105
+            if (isset($params['authType'])) {
106
+                $this->authType = $params['authType'];
107
+            }
108
+            if (isset($params['secure'])) {
109
+                if (is_string($params['secure'])) {
110
+                    $this->secure = ($params['secure'] === 'true');
111
+                } else {
112
+                    $this->secure = (bool)$params['secure'];
113
+                }
114
+            } else {
115
+                $this->secure = false;
116
+            }
117
+            if ($this->secure === true) {
118
+                // inject mock for testing
119
+                $this->certManager = \OC::$server->getCertificateManager();
120
+                if (is_null($this->certManager)) { //no user
121
+                    $this->certManager = \OC::$server->getCertificateManager(null);
122
+                }
123
+            }
124
+            $this->root = $params['root'] ?? '/';
125
+            $this->root = '/' . ltrim($this->root, '/');
126
+            $this->root = rtrim($this->root, '/') . '/';
127
+        } else {
128
+            throw new \Exception('Invalid webdav storage configuration');
129
+        }
130
+    }
131
+
132
+    protected function init() {
133
+        if ($this->ready) {
134
+            return;
135
+        }
136
+        $this->ready = true;
137
+
138
+        $settings = [
139
+            'baseUri' => $this->createBaseUri(),
140
+            'userName' => $this->user,
141
+            'password' => $this->password,
142
+        ];
143
+        if (isset($this->authType)) {
144
+            $settings['authType'] = $this->authType;
145
+        }
146
+
147
+        $proxy = \OC::$server->getConfig()->getSystemValue('proxy', '');
148
+        if ($proxy !== '') {
149
+            $settings['proxy'] = $proxy;
150
+        }
151
+
152
+        $this->client = new Client($settings);
153
+        $this->client->setThrowExceptions(true);
154
+
155
+        if($this->secure === true) {
156
+            $certPath = $this->certManager->getAbsoluteBundlePath();
157
+            if (file_exists($certPath)) {
158
+                $this->certPath = $certPath;
159
+            }
160
+            if ($this->certPath) {
161
+                $this->client->addCurlSetting(CURLOPT_CAINFO, $this->certPath);
162
+            }
163
+        }
164
+    }
165
+
166
+    /**
167
+     * Clear the stat cache
168
+     */
169
+    public function clearStatCache() {
170
+        $this->statCache->clear();
171
+    }
172
+
173
+    /** {@inheritdoc} */
174
+    public function getId() {
175
+        return 'webdav::' . $this->user . '@' . $this->host . '/' . $this->root;
176
+    }
177
+
178
+    /** {@inheritdoc} */
179
+    public function createBaseUri() {
180
+        $baseUri = 'http';
181
+        if ($this->secure) {
182
+            $baseUri .= 's';
183
+        }
184
+        $baseUri .= '://' . $this->host . $this->root;
185
+        return $baseUri;
186
+    }
187
+
188
+    /** {@inheritdoc} */
189
+    public function mkdir($path) {
190
+        $this->init();
191
+        $path = $this->cleanPath($path);
192
+        $result = $this->simpleResponse('MKCOL', $path, null, 201);
193
+        if ($result) {
194
+            $this->statCache->set($path, true);
195
+        }
196
+        return $result;
197
+    }
198
+
199
+    /** {@inheritdoc} */
200
+    public function rmdir($path) {
201
+        $this->init();
202
+        $path = $this->cleanPath($path);
203
+        // FIXME: some WebDAV impl return 403 when trying to DELETE
204
+        // a non-empty folder
205
+        $result = $this->simpleResponse('DELETE', $path . '/', null, 204);
206
+        $this->statCache->clear($path . '/');
207
+        $this->statCache->remove($path);
208
+        return $result;
209
+    }
210
+
211
+    /** {@inheritdoc} */
212
+    public function opendir($path) {
213
+        $this->init();
214
+        $path = $this->cleanPath($path);
215
+        try {
216
+            $response = $this->client->propFind(
217
+                $this->encodePath($path),
218
+                ['{DAV:}getetag'],
219
+                1
220
+            );
221
+            if ($response === false) {
222
+                return false;
223
+            }
224
+            $content = [];
225
+            $files = array_keys($response);
226
+            array_shift($files); //the first entry is the current directory
227
+
228
+            if (!$this->statCache->hasKey($path)) {
229
+                $this->statCache->set($path, true);
230
+            }
231
+            foreach ($files as $file) {
232
+                $file = urldecode($file);
233
+                // do not store the real entry, we might not have all properties
234
+                if (!$this->statCache->hasKey($path)) {
235
+                    $this->statCache->set($file, true);
236
+                }
237
+                $file = basename($file);
238
+                $content[] = $file;
239
+            }
240
+            return IteratorDirectory::wrap($content);
241
+        } catch (\Exception $e) {
242
+            $this->convertException($e, $path);
243
+        }
244
+        return false;
245
+    }
246
+
247
+    /**
248
+     * Propfind call with cache handling.
249
+     *
250
+     * First checks if information is cached.
251
+     * If not, request it from the server then store to cache.
252
+     *
253
+     * @param string $path path to propfind
254
+     *
255
+     * @return array|boolean propfind response or false if the entry was not found
256
+     *
257
+     * @throws ClientHttpException
258
+     */
259
+    protected function propfind($path) {
260
+        $path = $this->cleanPath($path);
261
+        $cachedResponse = $this->statCache->get($path);
262
+        // we either don't know it, or we know it exists but need more details
263
+        if (is_null($cachedResponse) || $cachedResponse === true) {
264
+            $this->init();
265
+            try {
266
+                $response = $this->client->propFind(
267
+                    $this->encodePath($path),
268
+                    [
269
+                        '{DAV:}getlastmodified',
270
+                        '{DAV:}getcontentlength',
271
+                        '{DAV:}getcontenttype',
272
+                        '{http://owncloud.org/ns}permissions',
273
+                        '{http://open-collaboration-services.org/ns}share-permissions',
274
+                        '{DAV:}resourcetype',
275
+                        '{DAV:}getetag',
276
+                    ]
277
+                );
278
+                $this->statCache->set($path, $response);
279
+            } catch (ClientHttpException $e) {
280
+                if ($e->getHttpStatus() === 404 || $e->getHttpStatus() === 405) {
281
+                    $this->statCache->clear($path . '/');
282
+                    $this->statCache->set($path, false);
283
+                    return false;
284
+                }
285
+                $this->convertException($e, $path);
286
+            } catch (\Exception $e) {
287
+                $this->convertException($e, $path);
288
+            }
289
+        } else {
290
+            $response = $cachedResponse;
291
+        }
292
+        return $response;
293
+    }
294
+
295
+    /** {@inheritdoc} */
296
+    public function filetype($path) {
297
+        try {
298
+            $response = $this->propfind($path);
299
+            if ($response === false) {
300
+                return false;
301
+            }
302
+            $responseType = [];
303
+            if (isset($response["{DAV:}resourcetype"])) {
304
+                /** @var ResourceType[] $response */
305
+                $responseType = $response["{DAV:}resourcetype"]->getValue();
306
+            }
307
+            return (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file';
308
+        } catch (\Exception $e) {
309
+            $this->convertException($e, $path);
310
+        }
311
+        return false;
312
+    }
313
+
314
+    /** {@inheritdoc} */
315
+    public function file_exists($path) {
316
+        try {
317
+            $path = $this->cleanPath($path);
318
+            $cachedState = $this->statCache->get($path);
319
+            if ($cachedState === false) {
320
+                // we know the file doesn't exist
321
+                return false;
322
+            } else if (!is_null($cachedState)) {
323
+                return true;
324
+            }
325
+            // need to get from server
326
+            return ($this->propfind($path) !== false);
327
+        } catch (\Exception $e) {
328
+            $this->convertException($e, $path);
329
+        }
330
+        return false;
331
+    }
332
+
333
+    /** {@inheritdoc} */
334
+    public function unlink($path) {
335
+        $this->init();
336
+        $path = $this->cleanPath($path);
337
+        $result = $this->simpleResponse('DELETE', $path, null, 204);
338
+        $this->statCache->clear($path . '/');
339
+        $this->statCache->remove($path);
340
+        return $result;
341
+    }
342
+
343
+    /** {@inheritdoc} */
344
+    public function fopen($path, $mode) {
345
+        $this->init();
346
+        $path = $this->cleanPath($path);
347
+        switch ($mode) {
348
+            case 'r':
349
+            case 'rb':
350
+                try {
351
+                    $response = $this->httpClientService
352
+                        ->newClient()
353
+                        ->get($this->createBaseUri() . $this->encodePath($path), [
354
+                            'auth' => [$this->user, $this->password],
355
+                            'stream' => true
356
+                        ]);
357
+                } catch (\GuzzleHttp\Exception\ClientException $e) {
358
+                    if ($e->getResponse() instanceof ResponseInterface
359
+                        && $e->getResponse()->getStatusCode() === 404) {
360
+                        return false;
361
+                    } else {
362
+                        throw $e;
363
+                    }
364
+                }
365
+
366
+                if ($response->getStatusCode() !== Http::STATUS_OK) {
367
+                    if ($response->getStatusCode() === Http::STATUS_LOCKED) {
368
+                        throw new \OCP\Lock\LockedException($path);
369
+                    } else {
370
+                        Util::writeLog("webdav client", 'Guzzle get returned status code ' . $response->getStatusCode(), ILogger::ERROR);
371
+                    }
372
+                }
373
+
374
+                return $response->getBody();
375
+            case 'w':
376
+            case 'wb':
377
+            case 'a':
378
+            case 'ab':
379
+            case 'r+':
380
+            case 'w+':
381
+            case 'wb+':
382
+            case 'a+':
383
+            case 'x':
384
+            case 'x+':
385
+            case 'c':
386
+            case 'c+':
387
+                //emulate these
388
+                $tempManager = \OC::$server->getTempManager();
389
+                if (strrpos($path, '.') !== false) {
390
+                    $ext = substr($path, strrpos($path, '.'));
391
+                } else {
392
+                    $ext = '';
393
+                }
394
+                if ($this->file_exists($path)) {
395
+                    if (!$this->isUpdatable($path)) {
396
+                        return false;
397
+                    }
398
+                    if ($mode === 'w' or $mode === 'w+') {
399
+                        $tmpFile = $tempManager->getTemporaryFile($ext);
400
+                    } else {
401
+                        $tmpFile = $this->getCachedFile($path);
402
+                    }
403
+                } else {
404
+                    if (!$this->isCreatable(dirname($path))) {
405
+                        return false;
406
+                    }
407
+                    $tmpFile = $tempManager->getTemporaryFile($ext);
408
+                }
409
+                $handle = fopen($tmpFile, $mode);
410
+                return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
411
+                    $this->writeBack($tmpFile, $path);
412
+                });
413
+        }
414
+    }
415
+
416
+    /**
417
+     * @param string $tmpFile
418
+     */
419
+    public function writeBack($tmpFile, $path) {
420
+        $this->uploadFile($tmpFile, $path);
421
+        unlink($tmpFile);
422
+    }
423
+
424
+    /** {@inheritdoc} */
425
+    public function free_space($path) {
426
+        $this->init();
427
+        $path = $this->cleanPath($path);
428
+        try {
429
+            // TODO: cacheable ?
430
+            $response = $this->client->propfind($this->encodePath($path), ['{DAV:}quota-available-bytes']);
431
+            if ($response === false) {
432
+                return FileInfo::SPACE_UNKNOWN;
433
+            }
434
+            if (isset($response['{DAV:}quota-available-bytes'])) {
435
+                return (int)$response['{DAV:}quota-available-bytes'];
436
+            } else {
437
+                return FileInfo::SPACE_UNKNOWN;
438
+            }
439
+        } catch (\Exception $e) {
440
+            return FileInfo::SPACE_UNKNOWN;
441
+        }
442
+    }
443
+
444
+    /** {@inheritdoc} */
445
+    public function touch($path, $mtime = null) {
446
+        $this->init();
447
+        if (is_null($mtime)) {
448
+            $mtime = time();
449
+        }
450
+        $path = $this->cleanPath($path);
451
+
452
+        // if file exists, update the mtime, else create a new empty file
453
+        if ($this->file_exists($path)) {
454
+            try {
455
+                $this->statCache->remove($path);
456
+                $this->client->proppatch($this->encodePath($path), ['{DAV:}lastmodified' => $mtime]);
457
+                // non-owncloud clients might not have accepted the property, need to recheck it
458
+                $response = $this->client->propfind($this->encodePath($path), ['{DAV:}getlastmodified'], 0);
459
+                if ($response === false) {
460
+                    return false;
461
+                }
462
+                if (isset($response['{DAV:}getlastmodified'])) {
463
+                    $remoteMtime = strtotime($response['{DAV:}getlastmodified']);
464
+                    if ($remoteMtime !== $mtime) {
465
+                        // server has not accepted the mtime
466
+                        return false;
467
+                    }
468
+                }
469
+            } catch (ClientHttpException $e) {
470
+                if ($e->getHttpStatus() === 501) {
471
+                    return false;
472
+                }
473
+                $this->convertException($e, $path);
474
+                return false;
475
+            } catch (\Exception $e) {
476
+                $this->convertException($e, $path);
477
+                return false;
478
+            }
479
+        } else {
480
+            $this->file_put_contents($path, '');
481
+        }
482
+        return true;
483
+    }
484
+
485
+    /**
486
+     * @param string $path
487
+     * @param string $data
488
+     * @return int
489
+     */
490
+    public function file_put_contents($path, $data) {
491
+        $path = $this->cleanPath($path);
492
+        $result = parent::file_put_contents($path, $data);
493
+        $this->statCache->remove($path);
494
+        return $result;
495
+    }
496
+
497
+    /**
498
+     * @param string $path
499
+     * @param string $target
500
+     */
501
+    protected function uploadFile($path, $target) {
502
+        $this->init();
503
+
504
+        // invalidate
505
+        $target = $this->cleanPath($target);
506
+        $this->statCache->remove($target);
507
+        $source = fopen($path, 'r');
508
+
509
+        $this->httpClientService
510
+            ->newClient()
511
+            ->put($this->createBaseUri() . $this->encodePath($target), [
512
+                'body' => $source,
513
+                'auth' => [$this->user, $this->password]
514
+            ]);
515
+
516
+        $this->removeCachedFile($target);
517
+    }
518
+
519
+    /** {@inheritdoc} */
520
+    public function rename($path1, $path2) {
521
+        $this->init();
522
+        $path1 = $this->cleanPath($path1);
523
+        $path2 = $this->cleanPath($path2);
524
+        try {
525
+            // overwrite directory ?
526
+            if ($this->is_dir($path2)) {
527
+                // needs trailing slash in destination
528
+                $path2 = rtrim($path2, '/') . '/';
529
+            }
530
+            $this->client->request(
531
+                'MOVE',
532
+                $this->encodePath($path1),
533
+                null,
534
+                [
535
+                    'Destination' => $this->createBaseUri() . $this->encodePath($path2),
536
+                ]
537
+            );
538
+            $this->statCache->clear($path1 . '/');
539
+            $this->statCache->clear($path2 . '/');
540
+            $this->statCache->set($path1, false);
541
+            $this->statCache->set($path2, true);
542
+            $this->removeCachedFile($path1);
543
+            $this->removeCachedFile($path2);
544
+            return true;
545
+        } catch (\Exception $e) {
546
+            $this->convertException($e);
547
+        }
548
+        return false;
549
+    }
550
+
551
+    /** {@inheritdoc} */
552
+    public function copy($path1, $path2) {
553
+        $this->init();
554
+        $path1 = $this->cleanPath($path1);
555
+        $path2 = $this->cleanPath($path2);
556
+        try {
557
+            // overwrite directory ?
558
+            if ($this->is_dir($path2)) {
559
+                // needs trailing slash in destination
560
+                $path2 = rtrim($path2, '/') . '/';
561
+            }
562
+            $this->client->request(
563
+                'COPY',
564
+                $this->encodePath($path1),
565
+                null,
566
+                [
567
+                    'Destination' => $this->createBaseUri() . $this->encodePath($path2),
568
+                ]
569
+            );
570
+            $this->statCache->clear($path2 . '/');
571
+            $this->statCache->set($path2, true);
572
+            $this->removeCachedFile($path2);
573
+            return true;
574
+        } catch (\Exception $e) {
575
+            $this->convertException($e);
576
+        }
577
+        return false;
578
+    }
579
+
580
+    /** {@inheritdoc} */
581
+    public function stat($path) {
582
+        try {
583
+            $response = $this->propfind($path);
584
+            if (!$response) {
585
+                return false;
586
+            }
587
+            return [
588
+                'mtime' => strtotime($response['{DAV:}getlastmodified']),
589
+                'size' => (int)isset($response['{DAV:}getcontentlength']) ? $response['{DAV:}getcontentlength'] : 0,
590
+            ];
591
+        } catch (\Exception $e) {
592
+            $this->convertException($e, $path);
593
+        }
594
+        return [];
595
+    }
596
+
597
+    /** {@inheritdoc} */
598
+    public function getMimeType($path) {
599
+        $remoteMimetype = $this->getMimeTypeFromRemote($path);
600
+        if ($remoteMimetype === 'application/octet-stream') {
601
+            return \OC::$server->getMimeTypeDetector()->detectPath($path);
602
+        } else {
603
+            return $remoteMimetype;
604
+        }
605
+    }
606
+
607
+    public function getMimeTypeFromRemote($path) {
608
+        try {
609
+            $response = $this->propfind($path);
610
+            if ($response === false) {
611
+                return false;
612
+            }
613
+            $responseType = [];
614
+            if (isset($response["{DAV:}resourcetype"])) {
615
+                /** @var ResourceType[] $response */
616
+                $responseType = $response["{DAV:}resourcetype"]->getValue();
617
+            }
618
+            $type = (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file';
619
+            if ($type == 'dir') {
620
+                return 'httpd/unix-directory';
621
+            } elseif (isset($response['{DAV:}getcontenttype'])) {
622
+                return $response['{DAV:}getcontenttype'];
623
+            } else {
624
+                return 'application/octet-stream';
625
+            }
626
+        } catch (\Exception $e) {
627
+            return false;
628
+        }
629
+    }
630
+
631
+    /**
632
+     * @param string $path
633
+     * @return string
634
+     */
635
+    public function cleanPath($path) {
636
+        if ($path === '') {
637
+            return $path;
638
+        }
639
+        $path = Filesystem::normalizePath($path);
640
+        // remove leading slash
641
+        return substr($path, 1);
642
+    }
643
+
644
+    /**
645
+     * URL encodes the given path but keeps the slashes
646
+     *
647
+     * @param string $path to encode
648
+     * @return string encoded path
649
+     */
650
+    protected function encodePath($path) {
651
+        // slashes need to stay
652
+        return str_replace('%2F', '/', rawurlencode($path));
653
+    }
654
+
655
+    /**
656
+     * @param string $method
657
+     * @param string $path
658
+     * @param string|resource|null $body
659
+     * @param int $expected
660
+     * @return bool
661
+     * @throws StorageInvalidException
662
+     * @throws StorageNotAvailableException
663
+     */
664
+    protected function simpleResponse($method, $path, $body, $expected) {
665
+        $path = $this->cleanPath($path);
666
+        try {
667
+            $response = $this->client->request($method, $this->encodePath($path), $body);
668
+            return $response['statusCode'] == $expected;
669
+        } catch (ClientHttpException $e) {
670
+            if ($e->getHttpStatus() === 404 && $method === 'DELETE') {
671
+                $this->statCache->clear($path . '/');
672
+                $this->statCache->set($path, false);
673
+                return false;
674
+            }
675
+
676
+            $this->convertException($e, $path);
677
+        } catch (\Exception $e) {
678
+            $this->convertException($e, $path);
679
+        }
680
+        return false;
681
+    }
682
+
683
+    /**
684
+     * check if curl is installed
685
+     */
686
+    public static function checkDependencies() {
687
+        return true;
688
+    }
689
+
690
+    /** {@inheritdoc} */
691
+    public function isUpdatable($path) {
692
+        return (bool)($this->getPermissions($path) & Constants::PERMISSION_UPDATE);
693
+    }
694
+
695
+    /** {@inheritdoc} */
696
+    public function isCreatable($path) {
697
+        return (bool)($this->getPermissions($path) & Constants::PERMISSION_CREATE);
698
+    }
699
+
700
+    /** {@inheritdoc} */
701
+    public function isSharable($path) {
702
+        return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE);
703
+    }
704
+
705
+    /** {@inheritdoc} */
706
+    public function isDeletable($path) {
707
+        return (bool)($this->getPermissions($path) & Constants::PERMISSION_DELETE);
708
+    }
709
+
710
+    /** {@inheritdoc} */
711
+    public function getPermissions($path) {
712
+        $this->init();
713
+        $path = $this->cleanPath($path);
714
+        $response = $this->propfind($path);
715
+        if ($response === false) {
716
+            return 0;
717
+        }
718
+        if (isset($response['{http://owncloud.org/ns}permissions'])) {
719
+            return $this->parsePermissions($response['{http://owncloud.org/ns}permissions']);
720
+        } else if ($this->is_dir($path)) {
721
+            return Constants::PERMISSION_ALL;
722
+        } else if ($this->file_exists($path)) {
723
+            return Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE;
724
+        } else {
725
+            return 0;
726
+        }
727
+    }
728
+
729
+    /** {@inheritdoc} */
730
+    public function getETag($path) {
731
+        $this->init();
732
+        $path = $this->cleanPath($path);
733
+        $response = $this->propfind($path);
734
+        if ($response === false) {
735
+            return null;
736
+        }
737
+        if (isset($response['{DAV:}getetag'])) {
738
+            $etag = trim($response['{DAV:}getetag'], '"');
739
+            if (strlen($etag) > 40) {
740
+                $etag = md5($etag);
741
+            }
742
+            return $etag;
743
+        }
744
+        return parent::getEtag($path);
745
+    }
746
+
747
+    /**
748
+     * @param string $permissionsString
749
+     * @return int
750
+     */
751
+    protected function parsePermissions($permissionsString) {
752
+        $permissions = Constants::PERMISSION_READ;
753
+        if (strpos($permissionsString, 'R') !== false) {
754
+            $permissions |= Constants::PERMISSION_SHARE;
755
+        }
756
+        if (strpos($permissionsString, 'D') !== false) {
757
+            $permissions |= Constants::PERMISSION_DELETE;
758
+        }
759
+        if (strpos($permissionsString, 'W') !== false) {
760
+            $permissions |= Constants::PERMISSION_UPDATE;
761
+        }
762
+        if (strpos($permissionsString, 'CK') !== false) {
763
+            $permissions |= Constants::PERMISSION_CREATE;
764
+            $permissions |= Constants::PERMISSION_UPDATE;
765
+        }
766
+        return $permissions;
767
+    }
768
+
769
+    /**
770
+     * check if a file or folder has been updated since $time
771
+     *
772
+     * @param string $path
773
+     * @param int $time
774
+     * @throws \OCP\Files\StorageNotAvailableException
775
+     * @return bool
776
+     */
777
+    public function hasUpdated($path, $time) {
778
+        $this->init();
779
+        $path = $this->cleanPath($path);
780
+        try {
781
+            // force refresh for $path
782
+            $this->statCache->remove($path);
783
+            $response = $this->propfind($path);
784
+            if ($response === false) {
785
+                if ($path === '') {
786
+                    // if root is gone it means the storage is not available
787
+                    throw new StorageNotAvailableException('root is gone');
788
+                }
789
+                return false;
790
+            }
791
+            if (isset($response['{DAV:}getetag'])) {
792
+                $cachedData = $this->getCache()->get($path);
793
+                $etag = null;
794
+                if (isset($response['{DAV:}getetag'])) {
795
+                    $etag = trim($response['{DAV:}getetag'], '"');
796
+                }
797
+                if (!empty($etag) && $cachedData['etag'] !== $etag) {
798
+                    return true;
799
+                } else if (isset($response['{http://open-collaboration-services.org/ns}share-permissions'])) {
800
+                    $sharePermissions = (int)$response['{http://open-collaboration-services.org/ns}share-permissions'];
801
+                    return $sharePermissions !== $cachedData['permissions'];
802
+                } else if (isset($response['{http://owncloud.org/ns}permissions'])) {
803
+                    $permissions = $this->parsePermissions($response['{http://owncloud.org/ns}permissions']);
804
+                    return $permissions !== $cachedData['permissions'];
805
+                } else {
806
+                    return false;
807
+                }
808
+            } else {
809
+                $remoteMtime = strtotime($response['{DAV:}getlastmodified']);
810
+                return $remoteMtime > $time;
811
+            }
812
+        } catch (ClientHttpException $e) {
813
+            if ($e->getHttpStatus() === 405) {
814
+                if ($path === '') {
815
+                    // if root is gone it means the storage is not available
816
+                    throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
817
+                }
818
+                return false;
819
+            }
820
+            $this->convertException($e, $path);
821
+            return false;
822
+        } catch (\Exception $e) {
823
+            $this->convertException($e, $path);
824
+            return false;
825
+        }
826
+    }
827
+
828
+    /**
829
+     * Interpret the given exception and decide whether it is due to an
830
+     * unavailable storage, invalid storage or other.
831
+     * This will either throw StorageInvalidException, StorageNotAvailableException
832
+     * or do nothing.
833
+     *
834
+     * @param Exception $e sabre exception
835
+     * @param string $path optional path from the operation
836
+     *
837
+     * @throws StorageInvalidException if the storage is invalid, for example
838
+     * when the authentication expired or is invalid
839
+     * @throws StorageNotAvailableException if the storage is not available,
840
+     * which might be temporary
841
+     * @throws ForbiddenException if the action is not allowed
842
+     */
843
+    protected function convertException(Exception $e, $path = '') {
844
+        \OC::$server->getLogger()->logException($e, ['app' => 'files_external', 'level' => ILogger::DEBUG]);
845
+        if ($e instanceof ClientHttpException) {
846
+            if ($e->getHttpStatus() === Http::STATUS_LOCKED) {
847
+                throw new \OCP\Lock\LockedException($path);
848
+            }
849
+            if ($e->getHttpStatus() === Http::STATUS_UNAUTHORIZED) {
850
+                // either password was changed or was invalid all along
851
+                throw new StorageInvalidException(get_class($e) . ': ' . $e->getMessage());
852
+            } else if ($e->getHttpStatus() === Http::STATUS_METHOD_NOT_ALLOWED) {
853
+                // ignore exception for MethodNotAllowed, false will be returned
854
+                return;
855
+            } else if ($e->getHttpStatus() === Http::STATUS_FORBIDDEN){
856
+                // The operation is forbidden. Fail somewhat gracefully
857
+                throw new ForbiddenException(get_class($e) . ':' . $e->getMessage());
858
+            }
859
+            throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
860
+        } else if ($e instanceof ClientException) {
861
+            // connection timeout or refused, server could be temporarily down
862
+            throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
863
+        } else if ($e instanceof \InvalidArgumentException) {
864
+            // parse error because the server returned HTML instead of XML,
865
+            // possibly temporarily down
866
+            throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
867
+        } else if (($e instanceof StorageNotAvailableException) || ($e instanceof StorageInvalidException)) {
868
+            // rethrow
869
+            throw $e;
870
+        }
871
+
872
+        // TODO: only log for now, but in the future need to wrap/rethrow exception
873
+    }
874 874
 }
Please login to merge, or discard this patch.
lib/private/SubAdmin.php 1 patch
Indentation   +246 added lines, -246 removed lines patch added patch discarded remove patch
@@ -38,252 +38,252 @@
 block discarded – undo
38 38
 
39 39
 class SubAdmin extends PublicEmitter implements ISubAdmin {
40 40
 
41
-	/** @var IUserManager */
42
-	private $userManager;
43
-
44
-	/** @var IGroupManager */
45
-	private $groupManager;
46
-
47
-	/** @var IDBConnection */
48
-	private $dbConn;
49
-
50
-	/**
51
-	 * @param IUserManager $userManager
52
-	 * @param IGroupManager $groupManager
53
-	 * @param IDBConnection $dbConn
54
-	 */
55
-	public function __construct(IUserManager $userManager,
56
-	                            IGroupManager $groupManager,
57
-								IDBConnection $dbConn) {
58
-		$this->userManager = $userManager;
59
-		$this->groupManager = $groupManager;
60
-		$this->dbConn = $dbConn;
61
-
62
-		$this->userManager->listen('\OC\User', 'postDelete', function($user) {
63
-			$this->post_deleteUser($user);
64
-		});
65
-		$this->groupManager->listen('\OC\Group', 'postDelete', function($group) {
66
-			$this->post_deleteGroup($group);
67
-		});
68
-	}
69
-
70
-	/**
71
-	 * add a SubAdmin
72
-	 * @param IUser $user user to be SubAdmin
73
-	 * @param IGroup $group group $user becomes subadmin of
74
-	 */
75
-	public function createSubAdmin(IUser $user, IGroup $group): void {
76
-		$qb = $this->dbConn->getQueryBuilder();
77
-
78
-		$qb->insert('group_admin')
79
-			->values([
80
-				'gid' => $qb->createNamedParameter($group->getGID()),
81
-				'uid' => $qb->createNamedParameter($user->getUID())
82
-			])
83
-			->execute();
84
-
85
-		$this->emit('\OC\SubAdmin', 'postCreateSubAdmin', [$user, $group]);
86
-		\OC_Hook::emit("OC_SubAdmin", "post_createSubAdmin", ["gid" => $group->getGID()]);
87
-	}
88
-
89
-	/**
90
-	 * delete a SubAdmin
91
-	 * @param IUser $user the user that is the SubAdmin
92
-	 * @param IGroup $group the group
93
-	 */
94
-	public function deleteSubAdmin(IUser $user, IGroup $group): void {
95
-		$qb = $this->dbConn->getQueryBuilder();
96
-
97
-		$qb->delete('group_admin')
98
-			->where($qb->expr()->eq('gid', $qb->createNamedParameter($group->getGID())))
99
-			->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID())))
100
-			->execute();
101
-
102
-		$this->emit('\OC\SubAdmin', 'postDeleteSubAdmin', [$user, $group]);
103
-		\OC_Hook::emit("OC_SubAdmin", "post_deleteSubAdmin", ["gid" => $group->getGID()]);
104
-	}
105
-
106
-	/**
107
-	 * get groups of a SubAdmin
108
-	 * @param IUser $user the SubAdmin
109
-	 * @return IGroup[]
110
-	 */
111
-	public function getSubAdminsGroups(IUser $user): array {
112
-		$qb = $this->dbConn->getQueryBuilder();
113
-
114
-		$result = $qb->select('gid')
115
-			->from('group_admin')
116
-			->where($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID())))
117
-			->execute();
118
-
119
-		$groups = [];
120
-		while($row = $result->fetch()) {
121
-			$group = $this->groupManager->get($row['gid']);
122
-			if(!is_null($group)) {
123
-				$groups[$group->getGID()] = $group;
124
-			}
125
-		}
126
-		$result->closeCursor();
127
-
128
-		return $groups;
129
-	}
130
-
131
-	/**
132
-	 * get an array of groupid and displayName for a user
133
-	 * @param IUser $user
134
-	 * @return array ['displayName' => displayname]
135
-	 */
136
-	public function getSubAdminsGroupsName(IUser $user): array {
137
-		return array_map(function($group) {
138
-			return ['displayName' => $group->getDisplayName()];
139
-		}, $this->getSubAdminsGroups($user));
140
-	}
141
-
142
-	/**
143
-	 * get SubAdmins of a group
144
-	 * @param IGroup $group the group
145
-	 * @return IUser[]
146
-	 */
147
-	public function getGroupsSubAdmins(IGroup $group): array {
148
-		$qb = $this->dbConn->getQueryBuilder();
149
-
150
-		$result = $qb->select('uid')
151
-			->from('group_admin')
152
-			->where($qb->expr()->eq('gid', $qb->createNamedParameter($group->getGID())))
153
-			->execute();
154
-
155
-		$users = [];
156
-		while($row = $result->fetch()) {
157
-			$user = $this->userManager->get($row['uid']);
158
-			if(!is_null($user)) {
159
-				$users[] = $user;
160
-			}
161
-		}
162
-		$result->closeCursor();
163
-
164
-		return $users;
165
-	}
166
-
167
-	/**
168
-	 * get all SubAdmins
169
-	 * @return array
170
-	 */
171
-	public function getAllSubAdmins(): array {
172
-		$qb = $this->dbConn->getQueryBuilder();
173
-
174
-		$result = $qb->select('*')
175
-			->from('group_admin')
176
-			->execute();
177
-
178
-		$subadmins = [];
179
-		while($row = $result->fetch()) {
180
-			$user = $this->userManager->get($row['uid']);
181
-			$group = $this->groupManager->get($row['gid']);
182
-			if(!is_null($user) && !is_null($group)) {
183
-				$subadmins[] = [
184
-					'user'  => $user,
185
-					'group' => $group
186
-				];
187
-			}
188
-		}
189
-		$result->closeCursor();
190
-
191
-		return $subadmins;
192
-	}
193
-
194
-	/**
195
-	 * checks if a user is a SubAdmin of a group
196
-	 * @param IUser $user
197
-	 * @param IGroup $group
198
-	 * @return bool
199
-	 */
200
-	public function isSubAdminOfGroup(IUser $user, IGroup $group): bool {
201
-		$qb = $this->dbConn->getQueryBuilder();
202
-
203
-		/*
41
+    /** @var IUserManager */
42
+    private $userManager;
43
+
44
+    /** @var IGroupManager */
45
+    private $groupManager;
46
+
47
+    /** @var IDBConnection */
48
+    private $dbConn;
49
+
50
+    /**
51
+     * @param IUserManager $userManager
52
+     * @param IGroupManager $groupManager
53
+     * @param IDBConnection $dbConn
54
+     */
55
+    public function __construct(IUserManager $userManager,
56
+                                IGroupManager $groupManager,
57
+                                IDBConnection $dbConn) {
58
+        $this->userManager = $userManager;
59
+        $this->groupManager = $groupManager;
60
+        $this->dbConn = $dbConn;
61
+
62
+        $this->userManager->listen('\OC\User', 'postDelete', function($user) {
63
+            $this->post_deleteUser($user);
64
+        });
65
+        $this->groupManager->listen('\OC\Group', 'postDelete', function($group) {
66
+            $this->post_deleteGroup($group);
67
+        });
68
+    }
69
+
70
+    /**
71
+     * add a SubAdmin
72
+     * @param IUser $user user to be SubAdmin
73
+     * @param IGroup $group group $user becomes subadmin of
74
+     */
75
+    public function createSubAdmin(IUser $user, IGroup $group): void {
76
+        $qb = $this->dbConn->getQueryBuilder();
77
+
78
+        $qb->insert('group_admin')
79
+            ->values([
80
+                'gid' => $qb->createNamedParameter($group->getGID()),
81
+                'uid' => $qb->createNamedParameter($user->getUID())
82
+            ])
83
+            ->execute();
84
+
85
+        $this->emit('\OC\SubAdmin', 'postCreateSubAdmin', [$user, $group]);
86
+        \OC_Hook::emit("OC_SubAdmin", "post_createSubAdmin", ["gid" => $group->getGID()]);
87
+    }
88
+
89
+    /**
90
+     * delete a SubAdmin
91
+     * @param IUser $user the user that is the SubAdmin
92
+     * @param IGroup $group the group
93
+     */
94
+    public function deleteSubAdmin(IUser $user, IGroup $group): void {
95
+        $qb = $this->dbConn->getQueryBuilder();
96
+
97
+        $qb->delete('group_admin')
98
+            ->where($qb->expr()->eq('gid', $qb->createNamedParameter($group->getGID())))
99
+            ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID())))
100
+            ->execute();
101
+
102
+        $this->emit('\OC\SubAdmin', 'postDeleteSubAdmin', [$user, $group]);
103
+        \OC_Hook::emit("OC_SubAdmin", "post_deleteSubAdmin", ["gid" => $group->getGID()]);
104
+    }
105
+
106
+    /**
107
+     * get groups of a SubAdmin
108
+     * @param IUser $user the SubAdmin
109
+     * @return IGroup[]
110
+     */
111
+    public function getSubAdminsGroups(IUser $user): array {
112
+        $qb = $this->dbConn->getQueryBuilder();
113
+
114
+        $result = $qb->select('gid')
115
+            ->from('group_admin')
116
+            ->where($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID())))
117
+            ->execute();
118
+
119
+        $groups = [];
120
+        while($row = $result->fetch()) {
121
+            $group = $this->groupManager->get($row['gid']);
122
+            if(!is_null($group)) {
123
+                $groups[$group->getGID()] = $group;
124
+            }
125
+        }
126
+        $result->closeCursor();
127
+
128
+        return $groups;
129
+    }
130
+
131
+    /**
132
+     * get an array of groupid and displayName for a user
133
+     * @param IUser $user
134
+     * @return array ['displayName' => displayname]
135
+     */
136
+    public function getSubAdminsGroupsName(IUser $user): array {
137
+        return array_map(function($group) {
138
+            return ['displayName' => $group->getDisplayName()];
139
+        }, $this->getSubAdminsGroups($user));
140
+    }
141
+
142
+    /**
143
+     * get SubAdmins of a group
144
+     * @param IGroup $group the group
145
+     * @return IUser[]
146
+     */
147
+    public function getGroupsSubAdmins(IGroup $group): array {
148
+        $qb = $this->dbConn->getQueryBuilder();
149
+
150
+        $result = $qb->select('uid')
151
+            ->from('group_admin')
152
+            ->where($qb->expr()->eq('gid', $qb->createNamedParameter($group->getGID())))
153
+            ->execute();
154
+
155
+        $users = [];
156
+        while($row = $result->fetch()) {
157
+            $user = $this->userManager->get($row['uid']);
158
+            if(!is_null($user)) {
159
+                $users[] = $user;
160
+            }
161
+        }
162
+        $result->closeCursor();
163
+
164
+        return $users;
165
+    }
166
+
167
+    /**
168
+     * get all SubAdmins
169
+     * @return array
170
+     */
171
+    public function getAllSubAdmins(): array {
172
+        $qb = $this->dbConn->getQueryBuilder();
173
+
174
+        $result = $qb->select('*')
175
+            ->from('group_admin')
176
+            ->execute();
177
+
178
+        $subadmins = [];
179
+        while($row = $result->fetch()) {
180
+            $user = $this->userManager->get($row['uid']);
181
+            $group = $this->groupManager->get($row['gid']);
182
+            if(!is_null($user) && !is_null($group)) {
183
+                $subadmins[] = [
184
+                    'user'  => $user,
185
+                    'group' => $group
186
+                ];
187
+            }
188
+        }
189
+        $result->closeCursor();
190
+
191
+        return $subadmins;
192
+    }
193
+
194
+    /**
195
+     * checks if a user is a SubAdmin of a group
196
+     * @param IUser $user
197
+     * @param IGroup $group
198
+     * @return bool
199
+     */
200
+    public function isSubAdminOfGroup(IUser $user, IGroup $group): bool {
201
+        $qb = $this->dbConn->getQueryBuilder();
202
+
203
+        /*
204 204
 		 * Primary key is ('gid', 'uid') so max 1 result possible here
205 205
 		 */
206
-		$result = $qb->select('*')
207
-			->from('group_admin')
208
-			->where($qb->expr()->eq('gid', $qb->createNamedParameter($group->getGID())))
209
-			->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID())))
210
-			->execute();
211
-
212
-		$fetch =  $result->fetch();
213
-		$result->closeCursor();
214
-		$result = !empty($fetch) ? true : false;
215
-
216
-		return $result;
217
-	}
218
-
219
-	/**
220
-	 * checks if a user is a SubAdmin
221
-	 * @param IUser $user
222
-	 * @return bool
223
-	 */
224
-	public function isSubAdmin(IUser $user): bool {
225
-		// Check if the user is already an admin
226
-		if ($this->groupManager->isAdmin($user->getUID())) {
227
-			return true;
228
-		}
229
-
230
-		$qb = $this->dbConn->getQueryBuilder();
231
-
232
-		$result = $qb->select('gid')
233
-			->from('group_admin')
234
-			->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID())))
235
-			->setMaxResults(1)
236
-			->execute();
237
-
238
-		$isSubAdmin = $result->fetch();
239
-		$result->closeCursor();
240
-
241
-		return $isSubAdmin !== false;
242
-	}
243
-
244
-	/**
245
-	 * checks if a user is a accessible by a subadmin
246
-	 * @param IUser $subadmin
247
-	 * @param IUser $user
248
-	 * @return bool
249
-	 */
250
-	public function isUserAccessible(IUser $subadmin, IUser $user): bool {
251
-		if(!$this->isSubAdmin($subadmin)) {
252
-			return false;
253
-		}
254
-		if($this->groupManager->isAdmin($user->getUID())) {
255
-			return false;
256
-		}
257
-		$accessibleGroups = $this->getSubAdminsGroups($subadmin);
258
-		foreach($accessibleGroups as $accessibleGroup) {
259
-			if($accessibleGroup->inGroup($user)) {
260
-				return true;
261
-			}
262
-		}
263
-		return false;
264
-	}
265
-
266
-	/**
267
-	 * delete all SubAdmins by $user
268
-	 * @param IUser $user
269
-	 */
270
-	private function post_deleteUser(IUser $user) {
271
-		$qb = $this->dbConn->getQueryBuilder();
272
-
273
-		$qb->delete('group_admin')
274
-			->where($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID())))
275
-			->execute();
276
-	}
277
-
278
-	/**
279
-	 * delete all SubAdmins by $group
280
-	 * @param IGroup $group
281
-	 */
282
-	private function post_deleteGroup(IGroup $group) {
283
-		$qb = $this->dbConn->getQueryBuilder();
284
-
285
-		$qb->delete('group_admin')
286
-			->where($qb->expr()->eq('gid', $qb->createNamedParameter($group->getGID())))
287
-			->execute();
288
-	}
206
+        $result = $qb->select('*')
207
+            ->from('group_admin')
208
+            ->where($qb->expr()->eq('gid', $qb->createNamedParameter($group->getGID())))
209
+            ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID())))
210
+            ->execute();
211
+
212
+        $fetch =  $result->fetch();
213
+        $result->closeCursor();
214
+        $result = !empty($fetch) ? true : false;
215
+
216
+        return $result;
217
+    }
218
+
219
+    /**
220
+     * checks if a user is a SubAdmin
221
+     * @param IUser $user
222
+     * @return bool
223
+     */
224
+    public function isSubAdmin(IUser $user): bool {
225
+        // Check if the user is already an admin
226
+        if ($this->groupManager->isAdmin($user->getUID())) {
227
+            return true;
228
+        }
229
+
230
+        $qb = $this->dbConn->getQueryBuilder();
231
+
232
+        $result = $qb->select('gid')
233
+            ->from('group_admin')
234
+            ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID())))
235
+            ->setMaxResults(1)
236
+            ->execute();
237
+
238
+        $isSubAdmin = $result->fetch();
239
+        $result->closeCursor();
240
+
241
+        return $isSubAdmin !== false;
242
+    }
243
+
244
+    /**
245
+     * checks if a user is a accessible by a subadmin
246
+     * @param IUser $subadmin
247
+     * @param IUser $user
248
+     * @return bool
249
+     */
250
+    public function isUserAccessible(IUser $subadmin, IUser $user): bool {
251
+        if(!$this->isSubAdmin($subadmin)) {
252
+            return false;
253
+        }
254
+        if($this->groupManager->isAdmin($user->getUID())) {
255
+            return false;
256
+        }
257
+        $accessibleGroups = $this->getSubAdminsGroups($subadmin);
258
+        foreach($accessibleGroups as $accessibleGroup) {
259
+            if($accessibleGroup->inGroup($user)) {
260
+                return true;
261
+            }
262
+        }
263
+        return false;
264
+    }
265
+
266
+    /**
267
+     * delete all SubAdmins by $user
268
+     * @param IUser $user
269
+     */
270
+    private function post_deleteUser(IUser $user) {
271
+        $qb = $this->dbConn->getQueryBuilder();
272
+
273
+        $qb->delete('group_admin')
274
+            ->where($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID())))
275
+            ->execute();
276
+    }
277
+
278
+    /**
279
+     * delete all SubAdmins by $group
280
+     * @param IGroup $group
281
+     */
282
+    private function post_deleteGroup(IGroup $group) {
283
+        $qb = $this->dbConn->getQueryBuilder();
284
+
285
+        $qb->delete('group_admin')
286
+            ->where($qb->expr()->eq('gid', $qb->createNamedParameter($group->getGID())))
287
+            ->execute();
288
+    }
289 289
 }
Please login to merge, or discard this patch.
lib/private/Template/Base.php 1 patch
Indentation   +155 added lines, -155 removed lines patch added patch discarded remove patch
@@ -32,160 +32,160 @@
 block discarded – undo
32 32
 use OCP\Defaults;
33 33
 
34 34
 class Base {
35
-	private $template; // The template
36
-	private $vars; // Vars
37
-
38
-	/** @var \OCP\IL10N */
39
-	private $l10n;
40
-
41
-	/** @var Defaults */
42
-	private $theme;
43
-
44
-	/**
45
-	 * @param string $template
46
-	 * @param string $requestToken
47
-	 * @param \OCP\IL10N $l10n
48
-	 * @param Defaults $theme
49
-	 */
50
-	public function __construct($template, $requestToken, $l10n, $theme ) {
51
-		$this->vars = [];
52
-		$this->vars['requesttoken'] = $requestToken;
53
-		$this->l10n = $l10n;
54
-		$this->template = $template;
55
-		$this->theme = $theme;
56
-	}
57
-
58
-	/**
59
-	 * @param string $serverRoot
60
-	 * @param string|false $app_dir
61
-	 * @param string $theme
62
-	 * @param string $app
63
-	 * @return string[]
64
-	 */
65
-	protected function getAppTemplateDirs($theme, $app, $serverRoot, $app_dir) {
66
-		// Check if the app is in the app folder or in the root
67
-		if( file_exists($app_dir.'/templates/' )) {
68
-			return [
69
-				$serverRoot.'/themes/'.$theme.'/apps/'.$app.'/templates/',
70
-				$app_dir.'/templates/',
71
-			];
72
-		}
73
-		return [
74
-			$serverRoot.'/themes/'.$theme.'/'.$app.'/templates/',
75
-			$serverRoot.'/'.$app.'/templates/',
76
-		];
77
-	}
78
-
79
-	/**
80
-	 * @param string $serverRoot
81
-	 * @param string $theme
82
-	 * @return string[]
83
-	 */
84
-	protected function getCoreTemplateDirs($theme, $serverRoot) {
85
-		return [
86
-			$serverRoot.'/themes/'.$theme.'/core/templates/',
87
-			$serverRoot.'/core/templates/',
88
-		];
89
-	}
90
-
91
-	/**
92
-	 * Assign variables
93
-	 * @param string $key key
94
-	 * @param array|bool|integer|string $value value
95
-	 * @return bool
96
-	 *
97
-	 * This function assigns a variable. It can be accessed via $_[$key] in
98
-	 * the template.
99
-	 *
100
-	 * If the key existed before, it will be overwritten
101
-	 */
102
-	public function assign( $key, $value) {
103
-		$this->vars[$key] = $value;
104
-		return true;
105
-	}
106
-
107
-	/**
108
-	 * Appends a variable
109
-	 * @param string $key key
110
-	 * @param mixed $value value
111
-	 *
112
-	 * This function assigns a variable in an array context. If the key already
113
-	 * exists, the value will be appended. It can be accessed via
114
-	 * $_[$key][$position] in the template.
115
-	 */
116
-	public function append( $key, $value ) {
117
-		if( array_key_exists( $key, $this->vars )) {
118
-			$this->vars[$key][] = $value;
119
-		}
120
-		else{
121
-			$this->vars[$key] = [ $value ];
122
-		}
123
-	}
124
-
125
-	/**
126
-	 * Prints the proceeded template
127
-	 * @return bool
128
-	 *
129
-	 * This function proceeds the template and prints its output.
130
-	 */
131
-	public function printPage() {
132
-		$data = $this->fetchPage();
133
-		if( $data === false ) {
134
-			return false;
135
-		}
136
-		else{
137
-			print $data;
138
-			return true;
139
-		}
140
-	}
141
-
142
-	/**
143
-	 * Process the template
144
-	 *
145
-	 * @param array|null $additionalParams
146
-	 * @return string This function processes the template.
147
-	 *
148
-	 * This function processes the template.
149
-	 */
150
-	public function fetchPage($additionalParams = null) {
151
-		return $this->load($this->template, $additionalParams);
152
-	}
153
-
154
-	/**
155
-	 * doing the actual work
156
-	 *
157
-	 * @param string $file
158
-	 * @param array|null $additionalParams
159
-	 * @return string content
160
-	 *
161
-	 * Includes the template file, fetches its output
162
-	 */
163
-	protected function load($file, $additionalParams = null) {
164
-		// Register the variables
165
-		$_ = $this->vars;
166
-		$l = $this->l10n;
167
-		$theme = $this->theme;
168
-
169
-		if(!is_null($additionalParams)) {
170
-			$_ = array_merge( $additionalParams, $this->vars );
171
-			foreach ($_ as $var => $value) {
172
-				${$var} = $value;
173
-			}
174
-		}
175
-
176
-		// Include
177
-		ob_start();
178
-		try {
179
-			include $file;
180
-			$data = ob_get_contents();
181
-		} catch (\Exception $e) {
182
-			@ob_end_clean();
183
-			throw $e;
184
-		}
185
-		@ob_end_clean();
186
-
187
-		// Return data
188
-		return $data;
189
-	}
35
+    private $template; // The template
36
+    private $vars; // Vars
37
+
38
+    /** @var \OCP\IL10N */
39
+    private $l10n;
40
+
41
+    /** @var Defaults */
42
+    private $theme;
43
+
44
+    /**
45
+     * @param string $template
46
+     * @param string $requestToken
47
+     * @param \OCP\IL10N $l10n
48
+     * @param Defaults $theme
49
+     */
50
+    public function __construct($template, $requestToken, $l10n, $theme ) {
51
+        $this->vars = [];
52
+        $this->vars['requesttoken'] = $requestToken;
53
+        $this->l10n = $l10n;
54
+        $this->template = $template;
55
+        $this->theme = $theme;
56
+    }
57
+
58
+    /**
59
+     * @param string $serverRoot
60
+     * @param string|false $app_dir
61
+     * @param string $theme
62
+     * @param string $app
63
+     * @return string[]
64
+     */
65
+    protected function getAppTemplateDirs($theme, $app, $serverRoot, $app_dir) {
66
+        // Check if the app is in the app folder or in the root
67
+        if( file_exists($app_dir.'/templates/' )) {
68
+            return [
69
+                $serverRoot.'/themes/'.$theme.'/apps/'.$app.'/templates/',
70
+                $app_dir.'/templates/',
71
+            ];
72
+        }
73
+        return [
74
+            $serverRoot.'/themes/'.$theme.'/'.$app.'/templates/',
75
+            $serverRoot.'/'.$app.'/templates/',
76
+        ];
77
+    }
78
+
79
+    /**
80
+     * @param string $serverRoot
81
+     * @param string $theme
82
+     * @return string[]
83
+     */
84
+    protected function getCoreTemplateDirs($theme, $serverRoot) {
85
+        return [
86
+            $serverRoot.'/themes/'.$theme.'/core/templates/',
87
+            $serverRoot.'/core/templates/',
88
+        ];
89
+    }
90
+
91
+    /**
92
+     * Assign variables
93
+     * @param string $key key
94
+     * @param array|bool|integer|string $value value
95
+     * @return bool
96
+     *
97
+     * This function assigns a variable. It can be accessed via $_[$key] in
98
+     * the template.
99
+     *
100
+     * If the key existed before, it will be overwritten
101
+     */
102
+    public function assign( $key, $value) {
103
+        $this->vars[$key] = $value;
104
+        return true;
105
+    }
106
+
107
+    /**
108
+     * Appends a variable
109
+     * @param string $key key
110
+     * @param mixed $value value
111
+     *
112
+     * This function assigns a variable in an array context. If the key already
113
+     * exists, the value will be appended. It can be accessed via
114
+     * $_[$key][$position] in the template.
115
+     */
116
+    public function append( $key, $value ) {
117
+        if( array_key_exists( $key, $this->vars )) {
118
+            $this->vars[$key][] = $value;
119
+        }
120
+        else{
121
+            $this->vars[$key] = [ $value ];
122
+        }
123
+    }
124
+
125
+    /**
126
+     * Prints the proceeded template
127
+     * @return bool
128
+     *
129
+     * This function proceeds the template and prints its output.
130
+     */
131
+    public function printPage() {
132
+        $data = $this->fetchPage();
133
+        if( $data === false ) {
134
+            return false;
135
+        }
136
+        else{
137
+            print $data;
138
+            return true;
139
+        }
140
+    }
141
+
142
+    /**
143
+     * Process the template
144
+     *
145
+     * @param array|null $additionalParams
146
+     * @return string This function processes the template.
147
+     *
148
+     * This function processes the template.
149
+     */
150
+    public function fetchPage($additionalParams = null) {
151
+        return $this->load($this->template, $additionalParams);
152
+    }
153
+
154
+    /**
155
+     * doing the actual work
156
+     *
157
+     * @param string $file
158
+     * @param array|null $additionalParams
159
+     * @return string content
160
+     *
161
+     * Includes the template file, fetches its output
162
+     */
163
+    protected function load($file, $additionalParams = null) {
164
+        // Register the variables
165
+        $_ = $this->vars;
166
+        $l = $this->l10n;
167
+        $theme = $this->theme;
168
+
169
+        if(!is_null($additionalParams)) {
170
+            $_ = array_merge( $additionalParams, $this->vars );
171
+            foreach ($_ as $var => $value) {
172
+                ${$var} = $value;
173
+            }
174
+        }
175
+
176
+        // Include
177
+        ob_start();
178
+        try {
179
+            include $file;
180
+            $data = ob_get_contents();
181
+        } catch (\Exception $e) {
182
+            @ob_end_clean();
183
+            throw $e;
184
+        }
185
+        @ob_end_clean();
186
+
187
+        // Return data
188
+        return $data;
189
+    }
190 190
 
191 191
 }
Please login to merge, or discard this patch.
lib/private/Template/ResourceLocator.php 1 patch
Indentation   +169 added lines, -169 removed lines patch added patch discarded remove patch
@@ -30,173 +30,173 @@
 block discarded – undo
30 30
 namespace OC\Template;
31 31
 
32 32
 abstract class ResourceLocator {
33
-	protected $theme;
34
-
35
-	protected $mapping;
36
-	protected $serverroot;
37
-	protected $thirdpartyroot;
38
-	protected $webroot;
39
-
40
-	protected $resources = [];
41
-
42
-	/** @var \OCP\ILogger */
43
-	protected $logger;
44
-
45
-	/**
46
-	 * @param \OCP\ILogger $logger
47
-	 * @param string $theme
48
-	 * @param array $core_map
49
-	 * @param array $party_map
50
-	 */
51
-	public function __construct(\OCP\ILogger $logger, $theme, $core_map, $party_map) {
52
-		$this->logger = $logger;
53
-		$this->theme = $theme;
54
-		$this->mapping = $core_map + $party_map;
55
-		$this->serverroot = key($core_map);
56
-		$this->thirdpartyroot = key($party_map);
57
-		$this->webroot = $this->mapping[$this->serverroot];
58
-	}
59
-
60
-	/**
61
-	 * @param string $resource
62
-	 */
63
-	abstract public function doFind($resource);
64
-
65
-	/**
66
-	 * @param string $resource
67
-	 */
68
-	abstract public function doFindTheme($resource);
69
-
70
-	/**
71
-	 * Finds the resources and adds them to the list
72
-	 *
73
-	 * @param array $resources
74
-	 */
75
-	public function find($resources) {
76
-		foreach ($resources as $resource) {
77
-			try {
78
-				$this->doFind($resource);
79
-			} catch (ResourceNotFoundException $e) {
80
-				$resourceApp = substr($resource, 0, strpos($resource, '/'));
81
-				$this->logger->debug('Could not find resource file "' . $e->getResourcePath() . '"', ['app' => $resourceApp]);
82
-			}
83
-		}
84
-		if (!empty($this->theme)) {
85
-			foreach ($resources as $resource) {
86
-				try {
87
-					$this->doFindTheme($resource);
88
-				} catch (ResourceNotFoundException $e) {
89
-					$resourceApp = substr($resource, 0, strpos($resource, '/'));
90
-					$this->logger->debug('Could not find resource file in theme "' . $e->getResourcePath() . '"', ['app' => $resourceApp]);
91
-				}
92
-			}
93
-		}
94
-	}
95
-
96
-	/**
97
-	 * append the $file resource if exist at $root
98
-	 *
99
-	 * @param string $root path to check
100
-	 * @param string $file the filename
101
-	 * @param string|null $webRoot base for path, default map $root to $webRoot
102
-	 * @return bool True if the resource was found, false otherwise
103
-	 */
104
-	protected function appendIfExist($root, $file, $webRoot = null) {
105
-		if (is_file($root.'/'.$file)) {
106
-			$this->append($root, $file, $webRoot, false);
107
-			return true;
108
-		}
109
-		return false;
110
-	}
111
-
112
-	/**
113
-	 * Attempt to find the webRoot
114
-	 *
115
-	 * traverse the potential web roots upwards in the path
116
-	 *
117
-	 * example:
118
-	 *   - root: /srv/www/apps/myapp
119
-	 *   - available mappings: ['/srv/www']
120
-	 *
121
-	 * First we check if a mapping for /srv/www/apps/myapp is available,
122
-	 * then /srv/www/apps, /srv/www/apps, /srv/www, ... until we find a
123
-	 * valid web root
124
-	 *
125
-	 * @param string $root
126
-	 * @return string|null The web root or null on failure
127
-	 */
128
-	protected function findWebRoot($root) {
129
-		$webRoot = null;
130
-		$tmpRoot = $root;
131
-
132
-		while ($webRoot === null) {
133
-			if (isset($this->mapping[$tmpRoot])) {
134
-				$webRoot = $this->mapping[$tmpRoot];
135
-				break;
136
-			}
137
-
138
-			if ($tmpRoot === '/') {
139
-				break;
140
-			}
141
-
142
-			$tmpRoot = dirname($tmpRoot);
143
-		}
144
-
145
-		if ($webRoot === null) {
146
-			$realpath = realpath($root);
147
-
148
-			if ($realpath && ($realpath !== $root)) {
149
-				return $this->findWebRoot($realpath);
150
-			}
151
-		}
152
-
153
-		return $webRoot;
154
-	}
155
-
156
-	/**
157
-	 * append the $file resource at $root
158
-	 *
159
-	 * @param string $root path to check
160
-	 * @param string $file the filename
161
-	 * @param string|null $webRoot base for path, default map $root to $webRoot
162
-	 * @param bool $throw Throw an exception, when the route does not exist
163
-	 * @throws ResourceNotFoundException Only thrown when $throw is true and the resource is missing
164
-	 */
165
-	protected function append($root, $file, $webRoot = null, $throw = true) {
166
-
167
-		if (!is_string($root)) {
168
-			if ($throw) {
169
-				throw new ResourceNotFoundException($file, $webRoot);
170
-			}
171
-			return;
172
-		}
173
-
174
-		if (!$webRoot) {
175
-			$webRoot = $this->findWebRoot($root);
176
-
177
-			if ($webRoot === null) {
178
-				$webRoot = '';
179
-				$this->logger->error('ResourceLocator can not find a web root (root: {root}, file: {file}, webRoot: {webRoot}, throw: {throw})', [
180
-					'app' => 'lib',
181
-					'root' => $root,
182
-					'file' => $file,
183
-					'webRoot' => $webRoot,
184
-					'throw' => $throw ? 'true' : 'false'
185
-				]);
186
-			}
187
-		}
188
-		$this->resources[] = [$root, $webRoot, $file];
189
-
190
-		if ($throw && !is_file($root . '/' . $file)) {
191
-			throw new ResourceNotFoundException($file, $webRoot);
192
-		}
193
-	}
194
-
195
-	/**
196
-	 * Returns the list of all resources that should be loaded
197
-	 * @return array
198
-	 */
199
-	public function getResources() {
200
-		return $this->resources;
201
-	}
33
+    protected $theme;
34
+
35
+    protected $mapping;
36
+    protected $serverroot;
37
+    protected $thirdpartyroot;
38
+    protected $webroot;
39
+
40
+    protected $resources = [];
41
+
42
+    /** @var \OCP\ILogger */
43
+    protected $logger;
44
+
45
+    /**
46
+     * @param \OCP\ILogger $logger
47
+     * @param string $theme
48
+     * @param array $core_map
49
+     * @param array $party_map
50
+     */
51
+    public function __construct(\OCP\ILogger $logger, $theme, $core_map, $party_map) {
52
+        $this->logger = $logger;
53
+        $this->theme = $theme;
54
+        $this->mapping = $core_map + $party_map;
55
+        $this->serverroot = key($core_map);
56
+        $this->thirdpartyroot = key($party_map);
57
+        $this->webroot = $this->mapping[$this->serverroot];
58
+    }
59
+
60
+    /**
61
+     * @param string $resource
62
+     */
63
+    abstract public function doFind($resource);
64
+
65
+    /**
66
+     * @param string $resource
67
+     */
68
+    abstract public function doFindTheme($resource);
69
+
70
+    /**
71
+     * Finds the resources and adds them to the list
72
+     *
73
+     * @param array $resources
74
+     */
75
+    public function find($resources) {
76
+        foreach ($resources as $resource) {
77
+            try {
78
+                $this->doFind($resource);
79
+            } catch (ResourceNotFoundException $e) {
80
+                $resourceApp = substr($resource, 0, strpos($resource, '/'));
81
+                $this->logger->debug('Could not find resource file "' . $e->getResourcePath() . '"', ['app' => $resourceApp]);
82
+            }
83
+        }
84
+        if (!empty($this->theme)) {
85
+            foreach ($resources as $resource) {
86
+                try {
87
+                    $this->doFindTheme($resource);
88
+                } catch (ResourceNotFoundException $e) {
89
+                    $resourceApp = substr($resource, 0, strpos($resource, '/'));
90
+                    $this->logger->debug('Could not find resource file in theme "' . $e->getResourcePath() . '"', ['app' => $resourceApp]);
91
+                }
92
+            }
93
+        }
94
+    }
95
+
96
+    /**
97
+     * append the $file resource if exist at $root
98
+     *
99
+     * @param string $root path to check
100
+     * @param string $file the filename
101
+     * @param string|null $webRoot base for path, default map $root to $webRoot
102
+     * @return bool True if the resource was found, false otherwise
103
+     */
104
+    protected function appendIfExist($root, $file, $webRoot = null) {
105
+        if (is_file($root.'/'.$file)) {
106
+            $this->append($root, $file, $webRoot, false);
107
+            return true;
108
+        }
109
+        return false;
110
+    }
111
+
112
+    /**
113
+     * Attempt to find the webRoot
114
+     *
115
+     * traverse the potential web roots upwards in the path
116
+     *
117
+     * example:
118
+     *   - root: /srv/www/apps/myapp
119
+     *   - available mappings: ['/srv/www']
120
+     *
121
+     * First we check if a mapping for /srv/www/apps/myapp is available,
122
+     * then /srv/www/apps, /srv/www/apps, /srv/www, ... until we find a
123
+     * valid web root
124
+     *
125
+     * @param string $root
126
+     * @return string|null The web root or null on failure
127
+     */
128
+    protected function findWebRoot($root) {
129
+        $webRoot = null;
130
+        $tmpRoot = $root;
131
+
132
+        while ($webRoot === null) {
133
+            if (isset($this->mapping[$tmpRoot])) {
134
+                $webRoot = $this->mapping[$tmpRoot];
135
+                break;
136
+            }
137
+
138
+            if ($tmpRoot === '/') {
139
+                break;
140
+            }
141
+
142
+            $tmpRoot = dirname($tmpRoot);
143
+        }
144
+
145
+        if ($webRoot === null) {
146
+            $realpath = realpath($root);
147
+
148
+            if ($realpath && ($realpath !== $root)) {
149
+                return $this->findWebRoot($realpath);
150
+            }
151
+        }
152
+
153
+        return $webRoot;
154
+    }
155
+
156
+    /**
157
+     * append the $file resource at $root
158
+     *
159
+     * @param string $root path to check
160
+     * @param string $file the filename
161
+     * @param string|null $webRoot base for path, default map $root to $webRoot
162
+     * @param bool $throw Throw an exception, when the route does not exist
163
+     * @throws ResourceNotFoundException Only thrown when $throw is true and the resource is missing
164
+     */
165
+    protected function append($root, $file, $webRoot = null, $throw = true) {
166
+
167
+        if (!is_string($root)) {
168
+            if ($throw) {
169
+                throw new ResourceNotFoundException($file, $webRoot);
170
+            }
171
+            return;
172
+        }
173
+
174
+        if (!$webRoot) {
175
+            $webRoot = $this->findWebRoot($root);
176
+
177
+            if ($webRoot === null) {
178
+                $webRoot = '';
179
+                $this->logger->error('ResourceLocator can not find a web root (root: {root}, file: {file}, webRoot: {webRoot}, throw: {throw})', [
180
+                    'app' => 'lib',
181
+                    'root' => $root,
182
+                    'file' => $file,
183
+                    'webRoot' => $webRoot,
184
+                    'throw' => $throw ? 'true' : 'false'
185
+                ]);
186
+            }
187
+        }
188
+        $this->resources[] = [$root, $webRoot, $file];
189
+
190
+        if ($throw && !is_file($root . '/' . $file)) {
191
+            throw new ResourceNotFoundException($file, $webRoot);
192
+        }
193
+    }
194
+
195
+    /**
196
+     * Returns the list of all resources that should be loaded
197
+     * @return array
198
+     */
199
+    public function getResources() {
200
+        return $this->resources;
201
+    }
202 202
 }
Please login to merge, or discard this patch.