1<?php
2/**
3 * @author Bernhard Posselt <dev@bernhard-posselt.com>
4 * @author Joas Schilling <coding@schilljs.com>
5 * @author Morris Jobke <hey@morrisjobke.de>
6 * @author Robin Appelman <icewind@owncloud.com>
7 * @author Roeland Jago Douma <rullzer@owncloud.com>
8 * @author Thomas Müller <thomas.mueller@tmit.eu>
9 * @author Vincent Petry <pvince81@owncloud.com>
10 *
11 * @copyright Copyright (c) 2018, ownCloud GmbH
12 * @license AGPL-3.0
13 *
14 * This code is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU Affero General Public License, version 3,
16 * as published by the Free Software Foundation.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Affero General Public License for more details.
22 *
23 * You should have received a copy of the GNU Affero General Public License, version 3,
24 * along with this program.  If not, see <http://www.gnu.org/licenses/>
25 *
26 */
27
28namespace OC\Files\Node;
29
30use OC\Files\Filesystem;
31use OCP\Files\FileInfo;
32use OCP\Files\InvalidPathException;
33use OCP\Files\NotFoundException;
34use OCP\Files\NotPermittedException;
35
36// FIXME: this class really should be abstract
37class Node implements \OCP\Files\Node {
38	/**
39	 * @var \OC\Files\View $view
40	 */
41	protected $view;
42
43	/**
44	 * @var \OC\Files\Node\Root $root
45	 */
46	protected $root;
47
48	/**
49	 * @var string $path
50	 */
51	protected $path;
52
53	/**
54	 * @var \OCP\Files\FileInfo
55	 */
56	protected $fileInfo;
57
58	/**
59	 * @param \OC\Files\View $view
60	 * @param \OC\Files\Node\Root $root
61	 * @param string $path
62	 * @param FileInfo $fileInfo
63	 */
64	public function __construct($root, $view, $path, $fileInfo = null) {
65		$this->view = $view;
66		$this->root = $root;
67		$this->path = $path;
68		$this->fileInfo = $fileInfo;
69	}
70
71	/**
72	 * Creates a Node of the same type that represents a non-existing path
73	 *
74	 * @param string $path path
75	 * @return string non-existing node class
76	 */
77	protected function createNonExistingNode($path) {
78		throw new \Exception('Must be implemented by subclasses');
79	}
80
81	/**
82	 * Returns the matching file info
83	 *
84	 * @return FileInfo
85	 * @throws InvalidPathException
86	 * @throws NotFoundException
87	 */
88	public function getFileInfo() {
89		if (!Filesystem::isValidPath($this->path)) {
90			throw new InvalidPathException();
91		}
92		if (!$this->fileInfo) {
93			$fileInfo = $this->view->getFileInfo($this->path);
94			if ($fileInfo instanceof FileInfo) {
95				$this->fileInfo = $fileInfo;
96			} else {
97				throw new NotFoundException();
98			}
99		}
100		return $this->fileInfo;
101	}
102
103	/**
104	 * @param string[] $hooks
105	 */
106	protected function sendHooks($hooks) {
107		foreach ($hooks as $hook) {
108			$this->root->emit('\OC\Files', $hook, [$this]);
109		}
110	}
111
112	/**
113	 * @param int $permissions
114	 * @return bool
115	 */
116	protected function checkPermissions($permissions) {
117		return ($this->getPermissions() & $permissions) === $permissions;
118	}
119
120	public function delete() {
121		return;
122	}
123
124	/**
125	 * @param int $mtime
126	 * @throws \OCP\Files\NotPermittedException
127	 */
128	public function touch($mtime = null) {
129		if ($this->checkPermissions(\OCP\Constants::PERMISSION_UPDATE)) {
130			$this->sendHooks(['preTouch']);
131			$this->view->touch($this->path, $mtime);
132			$this->sendHooks(['postTouch']);
133			if ($this->fileInfo) {
134				if ($mtime === null) {
135					$mtime = \time();
136				}
137				$this->fileInfo['mtime'] = $mtime;
138			}
139		} else {
140			throw new NotPermittedException();
141		}
142	}
143
144	/**
145	 * @return \OC\Files\Storage\Storage
146	 * @throws \OCP\Files\NotFoundException
147	 */
148	public function getStorage() {
149		list($storage, ) = $this->view->resolvePath($this->path);
150		return $storage;
151	}
152
153	/**
154	 * @return string
155	 */
156	public function getPath() {
157		return $this->path;
158	}
159
160	/**
161	 * @return string
162	 */
163	public function getInternalPath() {
164		list(, $internalPath) = $this->view->resolvePath($this->path);
165		return $internalPath;
166	}
167
168	/**
169	 * @return int
170	 * @throws InvalidPathException
171	 * @throws NotFoundException
172	 */
173	public function getId() {
174		return $this->getFileInfo()->getId();
175	}
176
177	/**
178	 * @return array
179	 */
180	public function stat() {
181		return $this->view->stat($this->path);
182	}
183
184	/**
185	 * @return int
186	 * @throws InvalidPathException
187	 * @throws NotFoundException
188	 */
189	public function getMTime() {
190		return $this->getFileInfo()->getMTime();
191	}
192
193	/**
194	 * @return int
195	 * @throws InvalidPathException
196	 * @throws NotFoundException
197	 */
198	public function getSize() {
199		return $this->getFileInfo()->getSize();
200	}
201
202	/**
203	 * @return string
204	 * @throws InvalidPathException
205	 * @throws NotFoundException
206	 */
207	public function getEtag() {
208		return $this->getFileInfo()->getEtag();
209	}
210
211	/**
212	 * @return int
213	 * @throws InvalidPathException
214	 * @throws NotFoundException
215	 */
216	public function getPermissions() {
217		return $this->getFileInfo()->getPermissions();
218	}
219
220	/**
221	 * @return bool
222	 * @throws InvalidPathException
223	 * @throws NotFoundException
224	 */
225	public function isReadable() {
226		return $this->getFileInfo()->isReadable();
227	}
228
229	/**
230	 * @return bool
231	 * @throws InvalidPathException
232	 * @throws NotFoundException
233	 */
234	public function isUpdateable() {
235		return $this->getFileInfo()->isUpdateable();
236	}
237
238	/**
239	 * @return bool
240	 * @throws InvalidPathException
241	 * @throws NotFoundException
242	 */
243	public function isDeletable() {
244		return $this->getFileInfo()->isDeletable();
245	}
246
247	/**
248	 * @return bool
249	 * @throws InvalidPathException
250	 * @throws NotFoundException
251	 */
252	public function isShareable() {
253		return $this->getFileInfo()->isShareable();
254	}
255
256	/**
257	 * @return bool
258	 * @throws InvalidPathException
259	 * @throws NotFoundException
260	 */
261	public function isCreatable() {
262		return $this->getFileInfo()->isCreatable();
263	}
264
265	/**
266	 * @return Node
267	 */
268	public function getParent() {
269		return $this->root->get(\dirname($this->path));
270	}
271
272	/**
273	 * @return string
274	 */
275	public function getName() {
276		return \basename($this->path);
277	}
278
279	/**
280	 * @param string $path
281	 * @return string
282	 */
283	protected function normalizePath($path) {
284		if ($path === '' or $path === '/') {
285			return '/';
286		}
287		//no windows style slashes
288		$path = \str_replace('\\', '/', $path);
289		//add leading slash
290		if ($path[0] !== '/') {
291			$path = '/' . $path;
292		}
293		//remove duplicate slashes
294		while (\strpos($path, '//') !== false) {
295			$path = \str_replace('//', '/', $path);
296		}
297		//remove trailing slash
298		$path = \rtrim($path, '/');
299
300		return $path;
301	}
302
303	/**
304	 * check if the requested path is valid
305	 *
306	 * @param string $path
307	 * @return bool
308	 */
309	public function isValidPath($path) {
310		if (!$path || $path[0] !== '/') {
311			$path = '/' . $path;
312		}
313		if (\strstr($path, '/../') || \strrchr($path, '/') === '/..') {
314			return false;
315		}
316		return true;
317	}
318
319	public function isMounted() {
320		return $this->getFileInfo()->isMounted();
321	}
322
323	public function isShared() {
324		return $this->getFileInfo()->isShared();
325	}
326
327	public function getMimeType() {
328		return $this->getFileInfo()->getMimetype();
329	}
330
331	public function getMimePart() {
332		return $this->getFileInfo()->getMimePart();
333	}
334
335	public function getType() {
336		return $this->getFileInfo()->getType();
337	}
338
339	public function isEncrypted() {
340		return $this->getFileInfo()->isEncrypted();
341	}
342
343	public function getMountPoint() {
344		return $this->getFileInfo()->getMountPoint();
345	}
346
347	public function getOwner() {
348		return $this->getFileInfo()->getOwner();
349	}
350
351	public function getChecksum() {
352		return;
353	}
354
355	/**
356	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
357	 * @throws \OCP\Lock\LockedException
358	 */
359	public function lock($type) {
360		$this->view->lockFile($this->path, $type);
361	}
362
363	/**
364	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
365	 * @throws \OCP\Lock\LockedException
366	 */
367	public function changeLock($type) {
368		$this->view->changeLock($this->path, $type);
369	}
370
371	/**
372	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
373	 * @throws \OCP\Lock\LockedException
374	 */
375	public function unlock($type) {
376		$this->view->unlockFile($this->path, $type);
377	}
378
379	/**
380	 * @param string $targetPath
381	 * @throws \OCP\Files\NotPermittedException if copy not allowed or failed
382	 * @return \OC\Files\Node\Node
383	 */
384	public function copy($targetPath) {
385		$targetPath = $this->normalizePath($targetPath);
386		$parent = $this->root->get(\dirname($targetPath));
387		if ($parent instanceof Folder and $this->isValidPath($targetPath) and $parent->isCreatable()) {
388			$nonExisting = $this->createNonExistingNode($targetPath);
389			$this->root->emit('\OC\Files', 'preCopy', [$this, $nonExisting]);
390			$this->root->emit('\OC\Files', 'preWrite', [$nonExisting]);
391			if (!$this->view->copy($this->path, $targetPath)) {
392				throw new NotPermittedException('Could not copy ' . $this->path . ' to ' . $targetPath);
393			}
394			$targetNode = $this->root->get($targetPath);
395			$this->root->emit('\OC\Files', 'postCopy', [$this, $targetNode]);
396			$this->root->emit('\OC\Files', 'postWrite', [$targetNode]);
397			return $targetNode;
398		} else {
399			throw new NotPermittedException('No permission to copy to path ' . $targetPath);
400		}
401	}
402
403	/**
404	 * @param string $targetPath
405	 * @throws \OCP\Files\NotPermittedException if move not allowed or failed
406	 * @return \OC\Files\Node\Node
407	 */
408	public function move($targetPath) {
409		$targetPath = $this->normalizePath($targetPath);
410		$parent = $this->root->get(\dirname($targetPath));
411		if ($parent instanceof Folder and $this->isValidPath($targetPath) and $parent->isCreatable()) {
412			$nonExisting = $this->createNonExistingNode($targetPath);
413			$this->root->emit('\OC\Files', 'preRename', [$this, $nonExisting]);
414			$this->root->emit('\OC\Files', 'preWrite', [$nonExisting]);
415			if (!$this->view->rename($this->path, $targetPath)) {
416				throw new NotPermittedException('Could not move ' . $this->path . ' to ' . $targetPath);
417			}
418			$targetNode = $this->root->get($targetPath);
419			$this->root->emit('\OC\Files', 'postRename', [$this, $targetNode]);
420			$this->root->emit('\OC\Files', 'postWrite', [$targetNode]);
421			$this->path = $targetPath;
422			return $targetNode;
423		} else {
424			throw new NotPermittedException('No permission to move to path ' . $targetPath);
425		}
426	}
427
428	public function getView() {
429		return $this->view;
430	}
431}
432