view.php 44.7 KB
Newer Older
1
2
<?php
/**
3
4
5
6
 * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
 * This file is licensed under the Affero General Public License version 3 or
 * later.
 * See the COPYING-README file.
7
 */
8
9


10
11
12
13
namespace OC\Files;

use OC\Files\Cache\Updater;
use OC\Files\Mount\MoveableMount;
14
use OCP\Files\FileNameTooLongException;
15
use OCP\Files\InvalidCharacterInPathException;
16
use OCP\Files\InvalidPathException;
17
use OCP\Files\ReservedWordException;
18

19
/**
Thomas Tanghus's avatar
Thomas Tanghus committed
20
21
22
23
 * Class to provide access to ownCloud filesystem via a "view", and methods for
 * working with files within that view (e.g. read, write, delete, etc.). Each
 * view is restricted to a set of directories via a virtual root. The default view
 * uses the currently logged in user's data directory as root (parts of
24
 * OC_Filesystem are merely a wrapper for OC\Files\View).
Thomas Tanghus's avatar
Thomas Tanghus committed
25
 *
Sam Tuke's avatar
Sam Tuke committed
26
27
28
 * Apps that need to access files outside of the user data folders (to modify files
 * belonging to a user other than the one currently logged in, for example) should
 * use this class directly rather than using OC_Filesystem, or making use of PHP's
Thomas Tanghus's avatar
Thomas Tanghus committed
29
 * built-in file manipulation functions. This will ensure all hooks and proxies
Sam Tuke's avatar
Sam Tuke committed
30
 * are triggered correctly.
Sam Tuke's avatar
Sam Tuke committed
31
 *
Thomas Tanghus's avatar
Thomas Tanghus committed
32
 * Filesystem functions are not called directly; they are passed to the correct
33
 * \OC\Files\Storage\Storage object
34
 */
Robin Appelman's avatar
Robin Appelman committed
35
class View {
36
	/** @var string */
Robin Appelman's avatar
Robin Appelman committed
37
	private $fakeRoot = '';
38

39
	/** @var \OC\Files\Cache\Updater */
40
41
	protected $updater;

42
43
44
45
	/**
	 * @param string $root
	 * @throws \Exception If $root contains an invalid path
	 */
46
	public function __construct($root = '') {
47
48
49
50
		if(!Filesystem::isValidPath($root)) {
			throw new \Exception();
		}

Robin Appelman's avatar
Robin Appelman committed
51
		$this->fakeRoot = $root;
52
		$this->updater = new Updater($this);
53
	}
54

Robin Appelman's avatar
Robin Appelman committed
55
	public function getAbsolutePath($path = '/') {
56
		$this->assertPathLength($path);
57
		if ($path === '') {
Robin Appelman's avatar
Robin Appelman committed
58
			$path = '/';
59
		}
Robin Appelman's avatar
Robin Appelman committed
60
61
		if ($path[0] !== '/') {
			$path = '/' . $path;
62
		}
Robin Appelman's avatar
Robin Appelman committed
63
		return $this->fakeRoot . $path;
64
	}
Bart Visscher's avatar
Bart Visscher committed
65

66
	/**
67
	 * change the root to a fake root
Robin Appelman's avatar
Robin Appelman committed
68
	 *
69
	 * @param string $fakeRoot
70
	 * @return boolean|null
Robin Appelman's avatar
Robin Appelman committed
71
	 */
Thomas Tanghus's avatar
Thomas Tanghus committed
72
	public function chroot($fakeRoot) {
Robin Appelman's avatar
Robin Appelman committed
73
74
75
		if (!$fakeRoot == '') {
			if ($fakeRoot[0] !== '/') {
				$fakeRoot = '/' . $fakeRoot;
76
77
			}
		}
Robin Appelman's avatar
Robin Appelman committed
78
		$this->fakeRoot = $fakeRoot;
79
80
81
82
	}

	/**
	 * get the fake root
Robin Appelman's avatar
Robin Appelman committed
83
	 *
84
85
	 * @return string
	 */
Thomas Tanghus's avatar
Thomas Tanghus committed
86
	public function getRoot() {
87
88
89
		return $this->fakeRoot;
	}

90
91
	/**
	 * get path relative to the root of the view
Robin Appelman's avatar
Robin Appelman committed
92
	 *
93
	 * @param string $path
94
95
	 * @return string
	 */
Thomas Tanghus's avatar
Thomas Tanghus committed
96
	public function getRelativePath($path) {
97
		$this->assertPathLength($path);
Robin Appelman's avatar
Robin Appelman committed
98
		if ($this->fakeRoot == '') {
99
100
			return $path;
		}
101
102
103
104
105

		if (rtrim($path,'/') === rtrim($this->fakeRoot, '/')) {
			return '/';
		}

Robin Appelman's avatar
Robin Appelman committed
106
		if (strpos($path, $this->fakeRoot) !== 0) {
107
			return null;
Robin Appelman's avatar
Robin Appelman committed
108
109
110
		} else {
			$path = substr($path, strlen($this->fakeRoot));
			if (strlen($path) === 0) {
111
				return '/';
Robin Appelman's avatar
Robin Appelman committed
112
			} else {
113
114
				return $path;
			}
115
116
		}
	}
Thomas Tanghus's avatar
Thomas Tanghus committed
117

118
	/**
Robin Appelman's avatar
Robin Appelman committed
119
	 * get the mountpoint of the storage object for a path
Bart Visscher's avatar
Bart Visscher committed
120
121
	 * ( note: because a storage is not always mounted inside the fakeroot, the
	 * returned mountpoint is relative to the absolute root of the filesystem
122
	 * and does not take the chroot into account )
Robin Appelman's avatar
Robin Appelman committed
123
	 *
124
	 * @param string $path
Robin Appelman's avatar
Robin Appelman committed
125
126
	 * @return string
	 */
Thomas Tanghus's avatar
Thomas Tanghus committed
127
	public function getMountPoint($path) {
128
		return Filesystem::getMountPoint($this->getAbsolutePath($path));
129
130
	}

Robin Appelman's avatar
Robin Appelman committed
131
132
133
134
	/**
	 * get the mountpoint of the storage object for a path
	 * ( note: because a storage is not always mounted inside the fakeroot, the
	 * returned mountpoint is relative to the absolute root of the filesystem
135
	 * and does not take the chroot into account )
Robin Appelman's avatar
Robin Appelman committed
136
137
138
139
140
141
142
143
	 *
	 * @param string $path
	 * @return \OCP\Files\Mount\IMountPoint
	 */
	public function getMount($path) {
		return Filesystem::getMountManager()->find($this->getAbsolutePath($path));
	}

144
145
146
147
	/**
	 * resolve a path to a storage and internal path
	 *
	 * @param string $path
148
	 * @return array an array consisting of the storage and the internal path
149
150
	 */
	public function resolvePath($path) {
151
152
153
		$a = $this->getAbsolutePath($path);
		$p = Filesystem::normalizePath($a);
		return Filesystem::resolvePath($p);
154
155
	}

156
	/**
Robin Appelman's avatar
Robin Appelman committed
157
	 * return the path to a local version of the file
Bart Visscher's avatar
Bart Visscher committed
158
159
	 * we need this because we can't know if a file is stored local or not from
	 * outside the filestorage and for some purposes a local file is needed
Robin Appelman's avatar
Robin Appelman committed
160
	 *
161
	 * @param string $path
Robin Appelman's avatar
Robin Appelman committed
162
163
	 * @return string
	 */
Thomas Tanghus's avatar
Thomas Tanghus committed
164
	public function getLocalFile($path) {
Robin Appelman's avatar
Robin Appelman committed
165
		$parent = substr($path, 0, strrpos($path, '/'));
166
		$path = $this->getAbsolutePath($path);
167
		list($storage, $internalPath) = Filesystem::resolvePath($path);
168
169
		if (Filesystem::isValidPath($parent) and $storage) {
			return $storage->getLocalFile($internalPath);
170
171
		} else {
			return null;
172
173
		}
	}
Robin Appelman's avatar
Robin Appelman committed
174

175
	/**
176
	 * @param string $path
177
178
179
	 * @return string
	 */
	public function getLocalFolder($path) {
Robin Appelman's avatar
Robin Appelman committed
180
		$parent = substr($path, 0, strrpos($path, '/'));
181
		$path = $this->getAbsolutePath($path);
182
		list($storage, $internalPath) = Filesystem::resolvePath($path);
183
184
		if (Filesystem::isValidPath($parent) and $storage) {
			return $storage->getLocalFolder($internalPath);
185
186
		} else {
			return null;
187
188
		}
	}
189
190

	/**
Thomas Tanghus's avatar
Thomas Tanghus committed
191
192
	 * the following functions operate with arguments and return values identical
	 * to those of their PHP built-in equivalents. Mostly they are merely wrappers
193
	 * for \OC\Files\Storage\Storage via basicOperation().
194
	 */
Thomas Tanghus's avatar
Thomas Tanghus committed
195
	public function mkdir($path) {
196
		return $this->basicOperation('mkdir', $path, array('create', 'write'));
197
	}
Robin Appelman's avatar
Robin Appelman committed
198

199
200
201
202
203
204
205
	/**
	 * remove mount point
	 *
	 * @param \OC\Files\Mount\MoveableMount $mount
	 * @param string $path relative to data/
	 * @return boolean
	 */
206
	protected function removeMount($mount, $path) {
207
		if ($mount instanceof MoveableMount) {
208
			// cut of /user/files to get the relative path to data/user/files
209
			$pathParts = explode('/', $path, 4);
210
			$relPath = '/' . $pathParts[3];
211
212
			\OC_Hook::emit(
				Filesystem::CLASSNAME, "umount",
213
				array(Filesystem::signal_param_path => $relPath)
214
215
216
217
218
			);
			$result = $mount->removeMount();
			if ($result) {
				\OC_Hook::emit(
					Filesystem::CLASSNAME, "post_umount",
219
					array(Filesystem::signal_param_path => $relPath)
220
221
222
223
224
225
226
227
228
229
230
				);
			}
			return $result;
		} else {
			// do not allow deleting the storage's root / the mount point
			// because for some storages it might delete the whole contents
			// but isn't supposed to work that way
			return false;
		}
	}

Lukas Reschke's avatar
Lukas Reschke committed
231
232
233
234
	/**
	 * @param string $path
	 * @return bool|mixed
	 */
Thomas Tanghus's avatar
Thomas Tanghus committed
235
	public function rmdir($path) {
236
		$absolutePath = $this->getAbsolutePath($path);
237
238
239
240
		$mount = Filesystem::getMountManager()->find($absolutePath);
		if ($mount->getInternalPath($absolutePath) === '') {
			return $this->removeMount($mount, $path);
		}
241
		if ($this->is_dir($path)) {
242
			return $this->basicOperation('rmdir', $path, array('delete'));
243
244
245
		} else {
			return false;
		}
246
	}
Robin Appelman's avatar
Robin Appelman committed
247

248
249
250
251
	/**
	 * @param string $path
	 * @return resource
	 */
Thomas Tanghus's avatar
Thomas Tanghus committed
252
253
	public function opendir($path) {
		return $this->basicOperation('opendir', $path, array('read'));
254
	}
Robin Appelman's avatar
Robin Appelman committed
255

256
257
258
259
	/**
	 * @param $handle
	 * @return mixed
	 */
Thomas Tanghus's avatar
Thomas Tanghus committed
260
	public function readdir($handle) {
261
		$fsLocal = new Storage\Local(array('datadir' => '/'));
Robin Appelman's avatar
Robin Appelman committed
262
		return $fsLocal->readdir($handle);
263
	}
Robin Appelman's avatar
Robin Appelman committed
264

265
266
267
268
	/**
	 * @param string $path
	 * @return bool|mixed
	 */
Thomas Tanghus's avatar
Thomas Tanghus committed
269
	public function is_dir($path) {
Robin Appelman's avatar
Robin Appelman committed
270
		if ($path == '/') {
271
272
			return true;
		}
Thomas Tanghus's avatar
Thomas Tanghus committed
273
		return $this->basicOperation('is_dir', $path);
274
	}
Robin Appelman's avatar
Robin Appelman committed
275

276
277
278
279
	/**
	 * @param string $path
	 * @return bool|mixed
	 */
Thomas Tanghus's avatar
Thomas Tanghus committed
280
	public function is_file($path) {
Robin Appelman's avatar
Robin Appelman committed
281
		if ($path == '/') {
282
283
			return false;
		}
Thomas Tanghus's avatar
Thomas Tanghus committed
284
		return $this->basicOperation('is_file', $path);
285
	}
Robin Appelman's avatar
Robin Appelman committed
286

287
288
289
290
	/**
	 * @param string $path
	 * @return mixed
	 */
Thomas Tanghus's avatar
Thomas Tanghus committed
291
292
	public function stat($path) {
		return $this->basicOperation('stat', $path);
293
	}
Robin Appelman's avatar
Robin Appelman committed
294

295
296
297
298
	/**
	 * @param string $path
	 * @return mixed
	 */
Thomas Tanghus's avatar
Thomas Tanghus committed
299
300
	public function filetype($path) {
		return $this->basicOperation('filetype', $path);
301
	}
Robin Appelman's avatar
Robin Appelman committed
302

303
304
305
306
	/**
	 * @param string $path
	 * @return mixed
	 */
Thomas Tanghus's avatar
Thomas Tanghus committed
307
308
	public function filesize($path) {
		return $this->basicOperation('filesize', $path);
309
	}
Robin Appelman's avatar
Robin Appelman committed
310

311
312
313
314
315
	/**
	 * @param string $path
	 * @return bool|mixed
	 * @throws \OCP\Files\InvalidPathException
	 */
Thomas Tanghus's avatar
Thomas Tanghus committed
316
	public function readfile($path) {
317
		$this->assertPathLength($path);
318
		@ob_end_clean();
Robin Appelman's avatar
Robin Appelman committed
319
		$handle = $this->fopen($path, 'rb');
320
		if ($handle) {
321
			$chunkSize = 8192; // 8 kB chunks
322
323
324
325
			while (!feof($handle)) {
				echo fread($handle, $chunkSize);
				flush();
			}
Robin Appelman's avatar
Robin Appelman committed
326
			$size = $this->filesize($path);
327
			return $size;
328
		}
329
		return false;
330
	}
Robin Appelman's avatar
Robin Appelman committed
331

332
333
334
335
	/**
	 * @param string $path
	 * @return mixed
	 */
336
337
338
	public function isCreatable($path) {
		return $this->basicOperation('isCreatable', $path);
	}
Robin Appelman's avatar
Robin Appelman committed
339

340
341
342
343
	/**
	 * @param string $path
	 * @return mixed
	 */
344
345
346
	public function isReadable($path) {
		return $this->basicOperation('isReadable', $path);
	}
Robin Appelman's avatar
Robin Appelman committed
347

348
349
350
351
	/**
	 * @param string $path
	 * @return mixed
	 */
352
353
354
	public function isUpdatable($path) {
		return $this->basicOperation('isUpdatable', $path);
	}
Robin Appelman's avatar
Robin Appelman committed
355

356
357
358
359
	/**
	 * @param string $path
	 * @return bool|mixed
	 */
360
	public function isDeletable($path) {
361
362
363
364
365
		$absolutePath = $this->getAbsolutePath($path);
		$mount = Filesystem::getMountManager()->find($absolutePath);
		if ($mount->getInternalPath($absolutePath) === '') {
			return $mount instanceof MoveableMount;
		}
366
		return $this->basicOperation('isDeletable', $path);
367
	}
Robin Appelman's avatar
Robin Appelman committed
368

369
370
371
372
	/**
	 * @param string $path
	 * @return mixed
	 */
373
374
	public function isSharable($path) {
		return $this->basicOperation('isSharable', $path);
375
	}
Robin Appelman's avatar
Robin Appelman committed
376

377
378
379
380
	/**
	 * @param string $path
	 * @return bool|mixed
	 */
Thomas Tanghus's avatar
Thomas Tanghus committed
381
	public function file_exists($path) {
Robin Appelman's avatar
Robin Appelman committed
382
		if ($path == '/') {
383
384
			return true;
		}
Thomas Tanghus's avatar
Thomas Tanghus committed
385
		return $this->basicOperation('file_exists', $path);
386
	}
Robin Appelman's avatar
Robin Appelman committed
387

388
389
390
391
	/**
	 * @param string $path
	 * @return mixed
	 */
Thomas Tanghus's avatar
Thomas Tanghus committed
392
393
	public function filemtime($path) {
		return $this->basicOperation('filemtime', $path);
394
	}
Robin Appelman's avatar
Robin Appelman committed
395

396
397
398
399
400
	/**
	 * @param string $path
	 * @param int|string $mtime
	 * @return bool
	 */
Robin Appelman's avatar
Robin Appelman committed
401
	public function touch($path, $mtime = null) {
402
403
404
		if (!is_null($mtime) and !is_numeric($mtime)) {
			$mtime = strtotime($mtime);
		}
405

406
		$hooks = array('touch');
407

408
		if (!$this->file_exists($path)) {
409
			$hooks[] = 'create';
410
411
			$hooks[] = 'write';
		}
412
		$result = $this->basicOperation('touch', $path, $hooks, $mtime);
413
		if (!$result) {
Jesus Macias's avatar
Jesus Macias committed
414
			// If create file fails because of permissions on external storage like SMB folders,
415
			// check file exists and return false if not.
416
			if (!$this->file_exists($path)) {
417
418
				return false;
			}
419
420
421
			if (is_null($mtime)) {
				$mtime = time();
			}
422
			//if native touch fails, we emulate it by changing the mtime in the cache
423
424
425
			$this->putFileInfo($path, array('mtime' => $mtime));
		}
		return true;
426
	}
Robin Appelman's avatar
Robin Appelman committed
427

428
429
430
431
	/**
	 * @param string $path
	 * @return mixed
	 */
Thomas Tanghus's avatar
Thomas Tanghus committed
432
433
434
	public function file_get_contents($path) {
		return $this->basicOperation('file_get_contents', $path, array('read'));
	}
Robin Appelman's avatar
Robin Appelman committed
435

436
437
438
439
440
	/**
	 * @param bool $exists
	 * @param string $path
	 * @param bool $run
	 */
Joas Schilling's avatar
Joas Schilling committed
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
	protected function emit_file_hooks_pre($exists, $path, &$run) {
		if (!$exists) {
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_create, array(
				Filesystem::signal_param_path => $this->getHookPath($path),
				Filesystem::signal_param_run => &$run,
			));
		} else {
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_update, array(
				Filesystem::signal_param_path => $this->getHookPath($path),
				Filesystem::signal_param_run => &$run,
			));
		}
		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, array(
			Filesystem::signal_param_path => $this->getHookPath($path),
			Filesystem::signal_param_run => &$run,
		));
	}

459
460
461
462
	/**
	 * @param bool $exists
	 * @param string $path
	 */
Joas Schilling's avatar
Joas Schilling committed
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
	protected function emit_file_hooks_post($exists, $path) {
		if (!$exists) {
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, array(
				Filesystem::signal_param_path => $this->getHookPath($path),
			));
		} else {
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, array(
				Filesystem::signal_param_path => $this->getHookPath($path),
			));
		}
		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, array(
			Filesystem::signal_param_path => $this->getHookPath($path),
		));
	}

478
479
480
481
482
	/**
	 * @param string $path
	 * @param mixed $data
	 * @return bool|mixed
	 */
Thomas Tanghus's avatar
Thomas Tanghus committed
483
	public function file_put_contents($path, $data) {
Robin Appelman's avatar
Robin Appelman committed
484
		if (is_resource($data)) { //not having to deal with streams in file_put_contents makes life easier
485
			$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
Bart Visscher's avatar
Bart Visscher committed
486
			if (\OC_FileProxy::runPreProxies('file_put_contents', $absolutePath, $data)
487
				and Filesystem::isValidPath($path)
488
				and !Filesystem::isFileBlacklisted($path)
489
			) {
490
491
492
				$path = $this->getRelativePath($absolutePath);
				$exists = $this->file_exists($path);
				$run = true;
493
				if ($this->shouldEmitHooks($path)) {
Joas Schilling's avatar
Joas Schilling committed
494
					$this->emit_file_hooks_pre($exists, $path, $run);
495
				}
Robin Appelman's avatar
Robin Appelman committed
496
				if (!$run) {
497
498
					return false;
				}
Robin Appelman's avatar
Robin Appelman committed
499
500
				$target = $this->fopen($path, 'w');
				if ($target) {
501
					list ($count, $result) = \OC_Helper::streamCopy($data, $target);
502
503
					fclose($target);
					fclose($data);
504
					$this->updater->update($path);
505
					if ($this->shouldEmitHooks($path) && $result !== false) {
Joas Schilling's avatar
Joas Schilling committed
506
						$this->emit_file_hooks_post($exists, $path);
507
					}
508
					\OC_FileProxy::runPostProxies('file_put_contents', $absolutePath, $count);
509
					return $result;
Robin Appelman's avatar
Robin Appelman committed
510
				} else {
511
					return false;
512
				}
513
514
			} else {
				return false;
515
			}
Robin Appelman's avatar
Robin Appelman committed
516
		} else {
517
			$hooks = ($this->file_exists($path)) ? array('update', 'write') : array('create', 'write');
518
			return $this->basicOperation('file_put_contents', $path, $hooks, $data);
519
		}
520
	}
Robin Appelman's avatar
Robin Appelman committed
521

522
523
524
525
	/**
	 * @param string $path
	 * @return bool|mixed
	 */
Thomas Tanghus's avatar
Thomas Tanghus committed
526
	public function unlink($path) {
Vincent Petry's avatar
Vincent Petry committed
527
528
529
530
531
532
		if ($path === '' || $path === '/') {
			// do not allow deleting the root
			return false;
		}
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
533
		$mount = Filesystem::getMountManager()->find($absolutePath . $postFix);
534
		if ($mount and $mount->getInternalPath($absolutePath) === '') {
535
			return $this->removeMount($mount, $absolutePath);
Vincent Petry's avatar
Vincent Petry committed
536
		}
537
		return $this->basicOperation('unlink', $path, array('delete'));
538
	}
Robin Appelman's avatar
Robin Appelman committed
539

540
541
	/**
	 * @param string $directory
Lukas Reschke's avatar
Lukas Reschke committed
542
	 * @return bool|mixed
543
	 */
Morris Jobke's avatar
Morris Jobke committed
544
	public function deleteAll($directory) {
545
		return $this->rmdir($directory);
546
	}
Robin Appelman's avatar
Robin Appelman committed
547

Lukas Reschke's avatar
Lukas Reschke committed
548
	/**
549
550
551
552
553
	 * Rename/move a file or folder from the source path to target path.
	 *
	 * @param string $path1 source path
	 * @param string $path2 target path
	 *
Lukas Reschke's avatar
Lukas Reschke committed
554
555
	 * @return bool|mixed
	 */
Thomas Tanghus's avatar
Thomas Tanghus committed
556
	public function rename($path1, $path2) {
Robin Appelman's avatar
Robin Appelman committed
557
558
		$postFix1 = (substr($path1, -1, 1) === '/') ? '/' : '';
		$postFix2 = (substr($path2, -1, 1) === '/') ? '/' : '';
559
560
		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
561
562
563
564
		if (
			\OC_FileProxy::runPreProxies('rename', $absolutePath1, $absolutePath2)
			and Filesystem::isValidPath($path2)
			and Filesystem::isValidPath($path1)
565
			and !Filesystem::isFileBlacklisted($path2)
566
		) {
Thomas Tanghus's avatar
Thomas Tanghus committed
567
568
			$path1 = $this->getRelativePath($absolutePath1);
			$path2 = $this->getRelativePath($absolutePath2);
569
			$exists = $this->file_exists($path2);
Bart Visscher's avatar
Bart Visscher committed
570

Robin Appelman's avatar
Robin Appelman committed
571
			if ($path1 == null or $path2 == null) {
572
573
				return false;
			}
Robin Appelman's avatar
Robin Appelman committed
574
			$run = true;
575
			if ($this->shouldEmitHooks() && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2))) {
576
				// if it was a rename from a part file to a regular file it was a write and not a rename operation
Joas Schilling's avatar
Joas Schilling committed
577
				$this->emit_file_hooks_pre($exists, $path2, $run);
578
			} elseif ($this->shouldEmitHooks()) {
579
580
				\OC_Hook::emit(
					Filesystem::CLASSNAME, Filesystem::signal_rename,
Robin Appelman's avatar
Robin Appelman committed
581
					array(
582
583
						Filesystem::signal_param_oldpath => $this->getHookPath($path1),
						Filesystem::signal_param_newpath => $this->getHookPath($path2),
584
						Filesystem::signal_param_run => &$run
Robin Appelman's avatar
Robin Appelman committed
585
					)
586
587
				);
			}
Robin Appelman's avatar
Robin Appelman committed
588
			if ($run) {
589
590
				$this->verifyPath(dirname($path2), basename($path2));

Robin Appelman's avatar
Robin Appelman committed
591
592
				$mp1 = $this->getMountPoint($path1 . $postFix1);
				$mp2 = $this->getMountPoint($path2 . $postFix2);
593
594
595
596
				$manager = Filesystem::getMountManager();
				$mount = $manager->find($absolutePath1 . $postFix1);
				$storage1 = $mount->getStorage();
				$internalPath1 = $mount->getInternalPath($absolutePath1 . $postFix1);
597
				list($storage2, $internalPath2) = Filesystem::resolvePath($absolutePath2 . $postFix2);
Robin Appelman's avatar
Robin Appelman committed
598
				if ($internalPath1 === '' and $mount instanceof MoveableMount) {
599
600
					if ($this->isTargetAllowed($absolutePath2)) {
						/**
601
						 * @var \OC\Files\Mount\MountPoint | \OC\Files\Mount\MoveableMount $mount
602
603
604
605
606
607
608
609
						 */
						$sourceMountPoint = $mount->getMountPoint();
						$result = $mount->moveMount($absolutePath2);
						$manager->moveMount($sourceMountPoint, $mount->getMountPoint());
						\OC_FileProxy::runPostProxies('rename', $absolutePath1, $absolutePath2);
					} else {
						$result = false;
					}
610
				} elseif ($mp1 == $mp2) {
611
					if ($storage1) {
612
						$result = $storage1->rename($internalPath1, $internalPath2);
613
						\OC_FileProxy::runPostProxies('rename', $absolutePath1, $absolutePath2);
614
615
					} else {
						$result = false;
616
					}
Thomas Tanghus's avatar
Thomas Tanghus committed
617
				} else {
618
					if ($this->is_dir($path1)) {
619
						$result = $this->copy($path1, $path2, true);
620
						if ($result === true) {
621
							$result = $storage1->rmdir($internalPath1);
622
623
624
625
						}
					} else {
						$source = $this->fopen($path1 . $postFix1, 'r');
						$target = $this->fopen($path2 . $postFix2, 'w');
626
627
628
629
						list(, $result) = \OC_Helper::streamCopy($source, $target);
						if ($result !== false) {
							$this->touch($path2, $this->filemtime($path1));
						}
630
631
632
633
634
635

						// close open handle - especially $source is necessary because unlink below will
						// throw an exception on windows because the file is locked
						fclose($source);
						fclose($target);

636
						if ($result !== false) {
637
							$result &= $storage1->unlink($internalPath1);
638
639
640
641
642
						} else {
							// delete partially written target file
							$storage2->unlink($internalPath2);
							// delete cache entry that was created by fopen
							$storage2->getCache()->remove($internalPath2);
643
						}
644
					}
Bart Visscher's avatar
Bart Visscher committed
645
				}
646
				if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
647
					// if it was a rename from a part file to a regular file it was a write and not a rename operation
648
649
650
651
					$this->updater->update($path2);
					if ($this->shouldEmitHooks()) {
						$this->emit_file_hooks_post($exists, $path2);
					}
652
				} elseif ($result) {
653
					$this->updater->rename($path1, $path2);
654
655
656
657
658
659
660
661
662
663
					if ($this->shouldEmitHooks($path1) and $this->shouldEmitHooks($path2)) {
						\OC_Hook::emit(
							Filesystem::CLASSNAME,
							Filesystem::signal_post_rename,
							array(
								Filesystem::signal_param_oldpath => $this->getHookPath($path1),
								Filesystem::signal_param_newpath => $this->getHookPath($path2)
							)
						);
					}
664
				}
665
				return $result;
666
667
			} else {
				return false;
668
			}
669
670
		} else {
			return false;
671
672
		}
	}
Robin Appelman's avatar
Robin Appelman committed
673

674
	/**
675
676
677
678
679
680
	 * Copy a file/folder from the source path to target path
	 *
	 * @param string $path1 source path
	 * @param string $path2 target path
	 * @param bool $preserveMtime whether to preserve mtime on the copy
	 *
681
682
	 * @return bool|mixed
	 */
683
	public function copy($path1, $path2, $preserveMtime = false) {
Robin Appelman's avatar
Robin Appelman committed
684
685
		$postFix1 = (substr($path1, -1, 1) === '/') ? '/' : '';
		$postFix2 = (substr($path2, -1, 1) === '/') ? '/' : '';
686
687
		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
688
689
690
691
		if (
			\OC_FileProxy::runPreProxies('copy', $absolutePath1, $absolutePath2)
			and Filesystem::isValidPath($path2)
			and Filesystem::isValidPath($path1)
692
			and !Filesystem::isFileBlacklisted($path2)
693
		) {
Thomas Tanghus's avatar
Thomas Tanghus committed
694
695
			$path1 = $this->getRelativePath($absolutePath1);
			$path2 = $this->getRelativePath($absolutePath2);
Bart Visscher's avatar
Bart Visscher committed
696

Robin Appelman's avatar
Robin Appelman committed
697
			if ($path1 == null or $path2 == null) {
698
699
				return false;
			}
Robin Appelman's avatar
Robin Appelman committed
700
			$run = true;
701
			$exists = $this->file_exists($path2);
702
			if ($this->shouldEmitHooks()) {
703
704
705
				\OC_Hook::emit(
					Filesystem::CLASSNAME,
					Filesystem::signal_copy,
Thomas Tanghus's avatar
Thomas Tanghus committed
706
					array(
707
708
						Filesystem::signal_param_oldpath => $this->getHookPath($path1),
						Filesystem::signal_param_newpath => $this->getHookPath($path2),
709
						Filesystem::signal_param_run => &$run
Thomas Tanghus's avatar
Thomas Tanghus committed
710
711
					)
				);
Joas Schilling's avatar
Joas Schilling committed
712
				$this->emit_file_hooks_pre($exists, $path2, $run);
713
			}
Robin Appelman's avatar
Robin Appelman committed
714
715
716
717
			if ($run) {
				$mp1 = $this->getMountPoint($path1 . $postFix1);
				$mp2 = $this->getMountPoint($path2 . $postFix2);
				if ($mp1 == $mp2) {
718
719
					list($storage, $internalPath1) = Filesystem::resolvePath($absolutePath1 . $postFix1);
					list(, $internalPath2) = Filesystem::resolvePath($absolutePath2 . $postFix2);
720
721
					if ($storage) {
						$result = $storage->copy($internalPath1, $internalPath2);
722
723
724
725
726
						if (!$result) {
							// delete partially written target file
							$storage->unlink($internalPath2);
							$storage->getCache()->remove($internalPath2);
						}
727
728
					} else {
						$result = false;
729
					}
Thomas Tanghus's avatar
Thomas Tanghus committed
730
				} else {
731
					if ($this->is_dir($path1) && ($dh = $this->opendir($path1))) {
732
						$result = $this->mkdir($path2);
733
734
735
						if ($preserveMtime) {
							$this->touch($path2, $this->filemtime($path1));
						}
736
						if (is_resource($dh)) {
737
738
							while (($file = readdir($dh)) !== false) {
								if (!Filesystem::isIgnoredDir($file)) {
739
									$result = $this->copy($path1 . '/' . $file, $path2 . '/' . $file, $preserveMtime);
740
								}
741
742
743
							}
						}
					} else {
744
						list($storage2, $internalPath2) = Filesystem::resolvePath($absolutePath2 . $postFix2);
745
746
						$source = $this->fopen($path1 . $postFix1, 'r');
						$target = $this->fopen($path2 . $postFix2, 'w');
747
748
						list(, $result) = \OC_Helper::streamCopy($source, $target);
						if($result && $preserveMtime) {
749
750
							$this->touch($path2, $this->filemtime($path1));
						}
751
752
						fclose($source);
						fclose($target);
753
754
755
756
757
						if (!$result) {
							// delete partially written target file
							$storage2->unlink($internalPath2);
							$storage2->getCache()->remove($internalPath2);
						}
758
					}
759
				}
760
				$this->updater->update($path2);
761
				if ($this->shouldEmitHooks() && $result !== false) {
762
763
764
					\OC_Hook::emit(
						Filesystem::CLASSNAME,
						Filesystem::signal_post_copy,
765
						array(
766
767
							Filesystem::signal_param_oldpath => $this->getHookPath($path1),
							Filesystem::signal_param_newpath => $this->getHookPath($path2)
768
769
						)
					);
Joas Schilling's avatar
Joas Schilling committed
770
					$this->emit_file_hooks_post($exists, $path2);
771
772
				}
				return $result;
773
774
			} else {
				return false;
775
			}
776
777
		} else {
			return false;
778
779
		}
	}
Robin Appelman's avatar
Robin Appelman committed
780

Robin McCorkell's avatar
Robin McCorkell committed
781
782
783
784
785
	/**
	 * @param string $path
	 * @param string $mode
	 * @return resource
	 */
Thomas Tanghus's avatar
Thomas Tanghus committed
786
	public function fopen($path, $mode) {
Robin Appelman's avatar
Robin Appelman committed
787
788
		$hooks = array();
		switch ($mode) {
789
			case 'r':
Robin Appelman's avatar
Robin Appelman committed
790
			case 'rb':
Robin Appelman's avatar
Robin Appelman committed
791
				$hooks[] = 'read';
792
793
				break;
			case 'r+':
Robin Appelman's avatar
Robin Appelman committed
794
			case 'rb+':
795
			case 'w+':
Robin Appelman's avatar
Robin Appelman committed
796
			case 'wb+':
797
			case 'x+':
Robin Appelman's avatar
Robin Appelman committed
798
			case 'xb+':
799
			case 'a+':
Robin Appelman's avatar
Robin Appelman committed
800
			case 'ab+':
Robin Appelman's avatar
Robin Appelman committed
801
802
				$hooks[] = 'read';
				$hooks[] = 'write';
803
804
				break;
			case 'w':
Robin Appelman's avatar
Robin Appelman committed
805
			case 'wb':
806
			case 'x':
Robin Appelman's avatar
Robin Appelman committed
807
			case 'xb':
808
			case 'a':
Robin Appelman's avatar
Robin Appelman committed
809
			case 'ab':
Robin Appelman's avatar
Robin Appelman committed
810
				$hooks[] = 'write';
811
812
				break;
			default:
813
				\OC_Log::write('core', 'invalid mode (' . $mode . ') for ' . $path, \OC_Log::ERROR);
814
815
		}

Thomas Tanghus's avatar
Thomas Tanghus committed
816
		return $this->basicOperation('fopen', $path, $hooks, $mode);
817
	}
Robin Appelman's avatar
Robin Appelman committed
818

819
820
821
822
823
	/**
	 * @param string $path
	 * @return bool|string
	 * @throws \OCP\Files\InvalidPathException
	 */
Thomas Tanghus's avatar
Thomas Tanghus committed
824
	public function toTmpFile($path) {
825
		$this->assertPathLength($path);
826
		if (Filesystem::isValidPath($path)) {
Thomas Tanghus's avatar
Thomas Tanghus committed
827
			$source = $this->fopen($path, 'r');
Robin Appelman's avatar
Robin Appelman committed
828
			if ($source) {
Thomas Müller's avatar
Thomas Müller committed
829
				$extension = pathinfo($path, PATHINFO_EXTENSION);
830
				$tmpFile = \OC_Helper::tmpFile($extension);
Thomas Tanghus's avatar
Thomas Tanghus committed
831
				file_put_contents($tmpFile, $source);
832
				return $tmpFile;
833
834
			} else {
				return false;
835
			}
836
837
		} else {
			return false;
838
839
		}
	}
Robin Appelman's avatar
Robin Appelman committed
840

841
842
843
844
845
846
	/**
	 * @param string $tmpFile
	 * @param string $path
	 * @return bool|mixed
	 * @throws \OCP\Files\InvalidPathException
	 */