1<?php
2/**
3 * @copyright Copyright (c) 2016, ownCloud, Inc.
4 *
5 * @author Christoph Wurst <christoph@winzerhof-wurst.at>
6 * @author J0WI <J0WI@users.noreply.github.com>
7 * @author Julius Härtl <jus@bitgrid.net>
8 * @author Lukas Reschke <lukas@statuscode.ch>
9 * @author Morris Jobke <hey@morrisjobke.de>
10 * @author Robin Appelman <robin@icewind.nl>
11 * @author Roeland Jago Douma <roeland@famdouma.nl>
12 * @author Tigran Mkrtchyan <tigran.mkrtchyan@desy.de>
13 *
14 * @license AGPL-3.0
15 *
16 * This code is free software: you can redistribute it and/or modify
17 * it under the terms of the GNU Affero General Public License, version 3,
18 * as published by the Free Software Foundation.
19 *
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU Affero General Public License for more details.
24 *
25 * You should have received a copy of the GNU Affero General Public License, version 3,
26 * along with this program. If not, see <http://www.gnu.org/licenses/>
27 *
28 */
29namespace OC\Files\Storage\Wrapper;
30
31use OC\Files\Cache\Wrapper\CacheJail;
32use OC\Files\Cache\Wrapper\JailPropagator;
33use OC\Files\Filesystem;
34use OCP\Files\Storage\IStorage;
35use OCP\Files\Storage\IWriteStreamStorage;
36use OCP\Lock\ILockingProvider;
37
38/**
39 * Jail to a subdirectory of the wrapped storage
40 *
41 * This restricts access to a subfolder of the wrapped storage with the subfolder becoming the root folder new storage
42 */
43class Jail extends Wrapper {
44	/**
45	 * @var string
46	 */
47	protected $rootPath;
48
49	/**
50	 * @param array $arguments ['storage' => $storage, 'root' => $root]
51	 *
52	 * $storage: The storage that will be wrapper
53	 * $root: The folder in the wrapped storage that will become the root folder of the wrapped storage
54	 */
55	public function __construct($arguments) {
56		parent::__construct($arguments);
57		$this->rootPath = $arguments['root'];
58	}
59
60	public function getUnjailedPath($path) {
61		return trim(Filesystem::normalizePath($this->rootPath . '/' . $path), '/');
62	}
63
64	/**
65	 * This is separate from Wrapper::getWrapperStorage so we can get the jailed storage consistently even if the jail is inside another wrapper
66	 */
67	public function getUnjailedStorage() {
68		return $this->storage;
69	}
70
71
72	public function getJailedPath($path) {
73		$root = rtrim($this->rootPath, '/') . '/';
74
75		if ($path !== $this->rootPath && strpos($path, $root) !== 0) {
76			return null;
77		} else {
78			$path = substr($path, strlen($this->rootPath));
79			return trim($path, '/');
80		}
81	}
82
83	public function getId() {
84		return parent::getId();
85	}
86
87	/**
88	 * see https://www.php.net/manual/en/function.mkdir.php
89	 *
90	 * @param string $path
91	 * @return bool
92	 */
93	public function mkdir($path) {
94		return $this->getWrapperStorage()->mkdir($this->getUnjailedPath($path));
95	}
96
97	/**
98	 * see https://www.php.net/manual/en/function.rmdir.php
99	 *
100	 * @param string $path
101	 * @return bool
102	 */
103	public function rmdir($path) {
104		return $this->getWrapperStorage()->rmdir($this->getUnjailedPath($path));
105	}
106
107	/**
108	 * see https://www.php.net/manual/en/function.opendir.php
109	 *
110	 * @param string $path
111	 * @return resource|bool
112	 */
113	public function opendir($path) {
114		return $this->getWrapperStorage()->opendir($this->getUnjailedPath($path));
115	}
116
117	/**
118	 * see https://www.php.net/manual/en/function.is_dir.php
119	 *
120	 * @param string $path
121	 * @return bool
122	 */
123	public function is_dir($path) {
124		return $this->getWrapperStorage()->is_dir($this->getUnjailedPath($path));
125	}
126
127	/**
128	 * see https://www.php.net/manual/en/function.is_file.php
129	 *
130	 * @param string $path
131	 * @return bool
132	 */
133	public function is_file($path) {
134		return $this->getWrapperStorage()->is_file($this->getUnjailedPath($path));
135	}
136
137	/**
138	 * see https://www.php.net/manual/en/function.stat.php
139	 * only the following keys are required in the result: size and mtime
140	 *
141	 * @param string $path
142	 * @return array|bool
143	 */
144	public function stat($path) {
145		return $this->getWrapperStorage()->stat($this->getUnjailedPath($path));
146	}
147
148	/**
149	 * see https://www.php.net/manual/en/function.filetype.php
150	 *
151	 * @param string $path
152	 * @return bool
153	 */
154	public function filetype($path) {
155		return $this->getWrapperStorage()->filetype($this->getUnjailedPath($path));
156	}
157
158	/**
159	 * see https://www.php.net/manual/en/function.filesize.php
160	 * The result for filesize when called on a folder is required to be 0
161	 *
162	 * @param string $path
163	 * @return int|bool
164	 */
165	public function filesize($path) {
166		return $this->getWrapperStorage()->filesize($this->getUnjailedPath($path));
167	}
168
169	/**
170	 * check if a file can be created in $path
171	 *
172	 * @param string $path
173	 * @return bool
174	 */
175	public function isCreatable($path) {
176		return $this->getWrapperStorage()->isCreatable($this->getUnjailedPath($path));
177	}
178
179	/**
180	 * check if a file can be read
181	 *
182	 * @param string $path
183	 * @return bool
184	 */
185	public function isReadable($path) {
186		return $this->getWrapperStorage()->isReadable($this->getUnjailedPath($path));
187	}
188
189	/**
190	 * check if a file can be written to
191	 *
192	 * @param string $path
193	 * @return bool
194	 */
195	public function isUpdatable($path) {
196		return $this->getWrapperStorage()->isUpdatable($this->getUnjailedPath($path));
197	}
198
199	/**
200	 * check if a file can be deleted
201	 *
202	 * @param string $path
203	 * @return bool
204	 */
205	public function isDeletable($path) {
206		return $this->getWrapperStorage()->isDeletable($this->getUnjailedPath($path));
207	}
208
209	/**
210	 * check if a file can be shared
211	 *
212	 * @param string $path
213	 * @return bool
214	 */
215	public function isSharable($path) {
216		return $this->getWrapperStorage()->isSharable($this->getUnjailedPath($path));
217	}
218
219	/**
220	 * get the full permissions of a path.
221	 * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
222	 *
223	 * @param string $path
224	 * @return int
225	 */
226	public function getPermissions($path) {
227		return $this->getWrapperStorage()->getPermissions($this->getUnjailedPath($path));
228	}
229
230	/**
231	 * see https://www.php.net/manual/en/function.file_exists.php
232	 *
233	 * @param string $path
234	 * @return bool
235	 */
236	public function file_exists($path) {
237		return $this->getWrapperStorage()->file_exists($this->getUnjailedPath($path));
238	}
239
240	/**
241	 * see https://www.php.net/manual/en/function.filemtime.php
242	 *
243	 * @param string $path
244	 * @return int|bool
245	 */
246	public function filemtime($path) {
247		return $this->getWrapperStorage()->filemtime($this->getUnjailedPath($path));
248	}
249
250	/**
251	 * see https://www.php.net/manual/en/function.file_get_contents.php
252	 *
253	 * @param string $path
254	 * @return string|bool
255	 */
256	public function file_get_contents($path) {
257		return $this->getWrapperStorage()->file_get_contents($this->getUnjailedPath($path));
258	}
259
260	/**
261	 * see https://www.php.net/manual/en/function.file_put_contents.php
262	 *
263	 * @param string $path
264	 * @param mixed $data
265	 * @return int|false
266	 */
267	public function file_put_contents($path, $data) {
268		return $this->getWrapperStorage()->file_put_contents($this->getUnjailedPath($path), $data);
269	}
270
271	/**
272	 * see https://www.php.net/manual/en/function.unlink.php
273	 *
274	 * @param string $path
275	 * @return bool
276	 */
277	public function unlink($path) {
278		return $this->getWrapperStorage()->unlink($this->getUnjailedPath($path));
279	}
280
281	/**
282	 * see https://www.php.net/manual/en/function.rename.php
283	 *
284	 * @param string $path1
285	 * @param string $path2
286	 * @return bool
287	 */
288	public function rename($path1, $path2) {
289		return $this->getWrapperStorage()->rename($this->getUnjailedPath($path1), $this->getUnjailedPath($path2));
290	}
291
292	/**
293	 * see https://www.php.net/manual/en/function.copy.php
294	 *
295	 * @param string $path1
296	 * @param string $path2
297	 * @return bool
298	 */
299	public function copy($path1, $path2) {
300		return $this->getWrapperStorage()->copy($this->getUnjailedPath($path1), $this->getUnjailedPath($path2));
301	}
302
303	/**
304	 * see https://www.php.net/manual/en/function.fopen.php
305	 *
306	 * @param string $path
307	 * @param string $mode
308	 * @return resource|bool
309	 */
310	public function fopen($path, $mode) {
311		return $this->getWrapperStorage()->fopen($this->getUnjailedPath($path), $mode);
312	}
313
314	/**
315	 * get the mimetype for a file or folder
316	 * The mimetype for a folder is required to be "httpd/unix-directory"
317	 *
318	 * @param string $path
319	 * @return string|bool
320	 */
321	public function getMimeType($path) {
322		return $this->getWrapperStorage()->getMimeType($this->getUnjailedPath($path));
323	}
324
325	/**
326	 * see https://www.php.net/manual/en/function.hash.php
327	 *
328	 * @param string $type
329	 * @param string $path
330	 * @param bool $raw
331	 * @return string|bool
332	 */
333	public function hash($type, $path, $raw = false) {
334		return $this->getWrapperStorage()->hash($type, $this->getUnjailedPath($path), $raw);
335	}
336
337	/**
338	 * see https://www.php.net/manual/en/function.free_space.php
339	 *
340	 * @param string $path
341	 * @return int|bool
342	 */
343	public function free_space($path) {
344		return $this->getWrapperStorage()->free_space($this->getUnjailedPath($path));
345	}
346
347	/**
348	 * search for occurrences of $query in file names
349	 *
350	 * @param string $query
351	 * @return array|bool
352	 */
353	public function search($query) {
354		return $this->getWrapperStorage()->search($query);
355	}
356
357	/**
358	 * see https://www.php.net/manual/en/function.touch.php
359	 * If the backend does not support the operation, false should be returned
360	 *
361	 * @param string $path
362	 * @param int $mtime
363	 * @return bool
364	 */
365	public function touch($path, $mtime = null) {
366		return $this->getWrapperStorage()->touch($this->getUnjailedPath($path), $mtime);
367	}
368
369	/**
370	 * get the path to a local version of the file.
371	 * The local version of the file can be temporary and doesn't have to be persistent across requests
372	 *
373	 * @param string $path
374	 * @return string|bool
375	 */
376	public function getLocalFile($path) {
377		return $this->getWrapperStorage()->getLocalFile($this->getUnjailedPath($path));
378	}
379
380	/**
381	 * check if a file or folder has been updated since $time
382	 *
383	 * @param string $path
384	 * @param int $time
385	 * @return bool
386	 *
387	 * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
388	 * returning true for other changes in the folder is optional
389	 */
390	public function hasUpdated($path, $time) {
391		return $this->getWrapperStorage()->hasUpdated($this->getUnjailedPath($path), $time);
392	}
393
394	/**
395	 * get a cache instance for the storage
396	 *
397	 * @param string $path
398	 * @param \OC\Files\Storage\Storage|null (optional) the storage to pass to the cache
399	 * @return \OC\Files\Cache\Cache
400	 */
401	public function getCache($path = '', $storage = null) {
402		if (!$storage) {
403			$storage = $this->getWrapperStorage();
404		}
405		$sourceCache = $this->getWrapperStorage()->getCache($this->getUnjailedPath($path), $storage);
406		return new CacheJail($sourceCache, $this->rootPath);
407	}
408
409	/**
410	 * get the user id of the owner of a file or folder
411	 *
412	 * @param string $path
413	 * @return string
414	 */
415	public function getOwner($path) {
416		return $this->getWrapperStorage()->getOwner($this->getUnjailedPath($path));
417	}
418
419	/**
420	 * get a watcher instance for the cache
421	 *
422	 * @param string $path
423	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
424	 * @return \OC\Files\Cache\Watcher
425	 */
426	public function getWatcher($path = '', $storage = null) {
427		if (!$storage) {
428			$storage = $this;
429		}
430		return $this->getWrapperStorage()->getWatcher($this->getUnjailedPath($path), $storage);
431	}
432
433	/**
434	 * get the ETag for a file or folder
435	 *
436	 * @param string $path
437	 * @return string|bool
438	 */
439	public function getETag($path) {
440		return $this->getWrapperStorage()->getETag($this->getUnjailedPath($path));
441	}
442
443	public function getMetaData($path) {
444		return $this->getWrapperStorage()->getMetaData($this->getUnjailedPath($path));
445	}
446
447	/**
448	 * @param string $path
449	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
450	 * @param \OCP\Lock\ILockingProvider $provider
451	 * @throws \OCP\Lock\LockedException
452	 */
453	public function acquireLock($path, $type, ILockingProvider $provider) {
454		$this->getWrapperStorage()->acquireLock($this->getUnjailedPath($path), $type, $provider);
455	}
456
457	/**
458	 * @param string $path
459	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
460	 * @param \OCP\Lock\ILockingProvider $provider
461	 */
462	public function releaseLock($path, $type, ILockingProvider $provider) {
463		$this->getWrapperStorage()->releaseLock($this->getUnjailedPath($path), $type, $provider);
464	}
465
466	/**
467	 * @param string $path
468	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
469	 * @param \OCP\Lock\ILockingProvider $provider
470	 */
471	public function changeLock($path, $type, ILockingProvider $provider) {
472		$this->getWrapperStorage()->changeLock($this->getUnjailedPath($path), $type, $provider);
473	}
474
475	/**
476	 * Resolve the path for the source of the share
477	 *
478	 * @param string $path
479	 * @return array
480	 */
481	public function resolvePath($path) {
482		return [$this->getWrapperStorage(), $this->getUnjailedPath($path)];
483	}
484
485	/**
486	 * @param IStorage $sourceStorage
487	 * @param string $sourceInternalPath
488	 * @param string $targetInternalPath
489	 * @return bool
490	 */
491	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
492		if ($sourceStorage === $this) {
493			return $this->copy($sourceInternalPath, $targetInternalPath);
494		}
495		return $this->getWrapperStorage()->copyFromStorage($sourceStorage, $sourceInternalPath, $this->getUnjailedPath($targetInternalPath));
496	}
497
498	/**
499	 * @param IStorage $sourceStorage
500	 * @param string $sourceInternalPath
501	 * @param string $targetInternalPath
502	 * @return bool
503	 */
504	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
505		if ($sourceStorage === $this) {
506			return $this->rename($sourceInternalPath, $targetInternalPath);
507		}
508		return $this->getWrapperStorage()->moveFromStorage($sourceStorage, $sourceInternalPath, $this->getUnjailedPath($targetInternalPath));
509	}
510
511	public function getPropagator($storage = null) {
512		if (isset($this->propagator)) {
513			return $this->propagator;
514		}
515
516		if (!$storage) {
517			$storage = $this;
518		}
519		$this->propagator = new JailPropagator($storage, \OC::$server->getDatabaseConnection());
520		return $this->propagator;
521	}
522
523	public function writeStream(string $path, $stream, int $size = null): int {
524		$storage = $this->getWrapperStorage();
525		if ($storage->instanceOfStorage(IWriteStreamStorage::class)) {
526			/** @var IWriteStreamStorage $storage */
527			return $storage->writeStream($this->getUnjailedPath($path), $stream, $size);
528		} else {
529			$target = $this->fopen($path, 'w');
530			[$count, $result] = \OC_Helper::streamCopy($stream, $target);
531			fclose($stream);
532			fclose($target);
533			return $count;
534		}
535	}
536
537	public function getDirectoryContent($directory): \Traversable {
538		return $this->getWrapperStorage()->getDirectoryContent($this->getUnjailedPath($directory));
539	}
540}
541