Completed
Push — master ( 113fd4...8500e1 )
by Björn
22:11 queued 01:07
created

Storage::shouldMoveToTrash()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 15
nc 7
nop 1
dl 0
loc 25
rs 8.439
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Björn Schießle <[email protected]>
6
 * @author Morris Jobke <[email protected]>
7
 * @author Robin Appelman <[email protected]>
8
 * @author Vincent Petry <[email protected]>
9
 *
10
 * @license AGPL-3.0
11
 *
12
 * This code is free software: you can redistribute it and/or modify
13
 * it under the terms of the GNU Affero General Public License, version 3,
14
 * as published by the Free Software Foundation.
15
 *
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
 * GNU Affero General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU Affero General Public License, version 3,
22
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
23
 *
24
 */
25
26
namespace OCA\Files_Trashbin;
27
28
use OC\Files\Filesystem;
29
use OC\Files\Storage\Wrapper\Wrapper;
30
use OC\Files\View;
31
use OCA\Files_Trashbin\Events\MoveToTrashEvent;
32
use OCP\Encryption\Exceptions\GenericEncryptionException;
33
use OCP\Files\IRootFolder;
34
use OCP\Files\Node;
35
use OCP\ILogger;
36
use OCP\IUserManager;
37
use Symfony\Component\EventDispatcher\EventDispatcher;
38
39
class Storage extends Wrapper {
40
41
	private $mountPoint;
42
	// remember already deleted files to avoid infinite loops if the trash bin
43
	// move files across storages
44
	private $deletedFiles = array();
45
46
	/**
47
	 * Disable trash logic
48
	 *
49
	 * @var bool
50
	 */
51
	private static $disableTrash = false;
52
53
	/**
54
	 * remember which file/folder was moved out of s shared folder
55
	 * in this case we want to add a copy to the owners trash bin
56
	 *
57
	 * @var array
58
	 */
59
	private static $moveOutOfSharedFolder = [];
60
61
	/** @var  IUserManager */
62
	private $userManager;
63
64
	/** @var ILogger */
65
	private $logger;
66
67
	/** @var EventDispatcher */
68
	private $eventDispatcher;
69
70
	/** @var IRootFolder */
71
	private $rootFolder;
72
73
	/**
74
	 * Storage constructor.
75
	 *
76
	 * @param array $parameters
77
	 * @param IUserManager|null $userManager
78
	 * @param ILogger|null $logger
79
	 * @param EventDispatcher|null $eventDispatcher
80
	 * @param IRootFolder|null $rootFolder
81
	 */
82
	public function __construct($parameters,
83
								IUserManager $userManager = null,
84
								ILogger $logger = null,
85
								EventDispatcher $eventDispatcher = null,
86
								IRootFolder $rootFolder = null) {
87
		$this->mountPoint = $parameters['mountPoint'];
88
		$this->userManager = $userManager;
89
		$this->logger = $logger;
90
		$this->eventDispatcher = $eventDispatcher;
91
		$this->rootFolder = $rootFolder;
92
		parent::__construct($parameters);
93
	}
94
95
	/**
96
	 * @internal
97
	 */
98
	public static function preRenameHook($params) {
99
		// in cross-storage cases, a rename is a copy + unlink,
100
		// that last unlink must not go to trash, only exception:
101
		// if the file was moved from a shared storage to a local folder,
102
		// in this case the owner should get a copy in his trash bin so that
103
		// they can restore the files again
104
105
		$oldPath = $params['oldpath'];
106
		$newPath = dirname($params['newpath']);
107
		$currentUser = \OC::$server->getUserSession()->getUser();
108
109
		$fileMovedOutOfSharedFolder = false;
110
111
		try {
112
			if ($currentUser) {
113
				$currentUserId = $currentUser->getUID();
114
115
				$view = new View($currentUserId . '/files');
116
				$fileInfo = $view->getFileInfo($oldPath);
117
				if ($fileInfo) {
118
					$sourceStorage = $fileInfo->getStorage();
119
					$sourceOwner = $view->getOwner($oldPath);
120
					$targetOwner = $view->getOwner($newPath);
121
122
					if ($sourceOwner !== $targetOwner
123
						&& $sourceStorage->instanceOfStorage('OCA\Files_Sharing\SharedStorage')
124
					) {
125
						$fileMovedOutOfSharedFolder = true;
126
					}
127
				}
128
			}
129
		} catch (\Exception $e) {
130
			// do nothing, in this case we just disable the trashbin and continue
131
			$logger = \OC::$server->getLogger();
132
			$logger->debug('Trashbin storage could not check if a file was moved out of a shared folder: ' . $e->getMessage());
133
		}
134
135
		if($fileMovedOutOfSharedFolder) {
136
			self::$moveOutOfSharedFolder['/' . $currentUserId . '/files' . $oldPath] = true;
0 ignored issues
show
Bug introduced by
The variable $currentUserId does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
137
		} else {
138
			self::$disableTrash = true;
139
		}
140
141
	}
142
143
	/**
144
	 * @internal
145
	 */
146
	public static function postRenameHook($params) {
0 ignored issues
show
Unused Code introduced by
The parameter $params is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
147
		self::$disableTrash = false;
148
	}
149
150
	/**
151
	 * Rename path1 to path2 by calling the wrapped storage.
152
	 *
153
	 * @param string $path1 first path
154
	 * @param string $path2 second path
155
	 * @return bool
156
	 */
157
	public function rename($path1, $path2) {
158
		$result = $this->storage->rename($path1, $path2);
159
		if ($result === false) {
160
			// when rename failed, the post_rename hook isn't triggered,
161
			// but we still want to reenable the trash logic
162
			self::$disableTrash = false;
163
		}
164
		return $result;
165
	}
166
167
	/**
168
	 * Deletes the given file by moving it into the trashbin.
169
	 *
170
	 * @param string $path path of file or folder to delete
171
	 *
172
	 * @return bool true if the operation succeeded, false otherwise
173
	 */
174
	public function unlink($path) {
175
		try {
176
			if (isset(self::$moveOutOfSharedFolder[$this->mountPoint . $path])) {
177
				$result = $this->doDelete($path, 'unlink', true);
178
				unset(self::$moveOutOfSharedFolder[$this->mountPoint . $path]);
179
			} else {
180
				$result = $this->doDelete($path, 'unlink');
181
			}
182
		} catch (GenericEncryptionException $e) {
183
			// in case of a encryption exception we delete the file right away
184
			$this->logger->info(
185
				"Can't move file" .  $path .
186
				"to the trash bin, therefore it was deleted right away");
187
188
			$result = $this->storage->unlink($path);
189
		}
190
191
		return $result;
192
	}
193
194
	/**
195
	 * Deletes the given folder by moving it into the trashbin.
196
	 *
197
	 * @param string $path path of folder to delete
198
	 *
199
	 * @return bool true if the operation succeeded, false otherwise
200
	 */
201
	public function rmdir($path) {
202
		if (isset(self::$moveOutOfSharedFolder[$this->mountPoint . $path])) {
203
			$result = $this->doDelete($path, 'rmdir', true);
204
			unset(self::$moveOutOfSharedFolder[$this->mountPoint . $path]);
205
		} else {
206
			$result = $this->doDelete($path, 'rmdir');
207
		}
208
209
		return $result;
210
	}
211
212
	/**
213
	 * check if it is a file located in data/user/files only files in the
214
	 * 'files' directory should be moved to the trash
215
	 *
216
	 * @param $path
217
	 * @return bool
218
	 */
219
	protected function shouldMoveToTrash($path){
220
221
		// check if there is a app which want to disable the trash bin for this file
222
		$fileId = $this->storage->getCache()->getId($path);
223
		$nodes = $this->rootFolder->getById($fileId);
224
		foreach ($nodes as $node) {
225
			$event = $this->createMoveToTrashEvent($node);
226
			$this->eventDispatcher->dispatch('OCA\Files_Trashbin::moveToTrash', $event);
227
			if ($event->shouldMoveToTrashBin() === false) {
228
				return false;
229
			}
230
		}
231
232
		$normalized = Filesystem::normalizePath($this->mountPoint . '/' . $path);
233
		$parts = explode('/', $normalized);
234
		if (count($parts) < 4) {
235
			return false;
236
		}
237
238
		if ($this->userManager->userExists($parts[1]) && $parts[2] === 'files') {
239
			return true;
240
		}
241
242
		return false;
243
	}
244
245
	/**
246
	 * get move to trash event
247
	 *
248
	 * @param Node $node
249
	 * @return MoveToTrashEvent
250
	 */
251
	protected function createMoveToTrashEvent(Node $node) {
252
		$event = new MoveToTrashEvent($node);
253
		return $event;
254
	}
255
256
	/**
257
	 * Run the delete operation with the given method
258
	 *
259
	 * @param string $path path of file or folder to delete
260
	 * @param string $method either "unlink" or "rmdir"
261
	 * @param bool $ownerOnly delete for owner only (if file gets moved out of a shared folder)
262
	 *
263
	 * @return bool true if the operation succeeded, false otherwise
264
	 */
265
	private function doDelete($path, $method, $ownerOnly = false) {
266
		if (self::$disableTrash
267
			|| !\OC_App::isEnabled('files_trashbin')
268
			|| (pathinfo($path, PATHINFO_EXTENSION) === 'part')
269
			|| $this->shouldMoveToTrash($path) === false
270
		) {
271
			return call_user_func_array([$this->storage, $method], [$path]);
272
		}
273
274
		// check permissions before we continue, this is especially important for
275
		// shared files
276
		if (!$this->isDeletable($path)) {
277
			return false;
278
		}
279
280
		$normalized = Filesystem::normalizePath($this->mountPoint . '/' . $path, true, false, true);
281
		$result = true;
282
		$view = Filesystem::getView();
283
		if (!isset($this->deletedFiles[$normalized]) && $view instanceof View) {
284
			$this->deletedFiles[$normalized] = $normalized;
285
			if ($filesPath = $view->getRelativePath($normalized)) {
286
				$filesPath = trim($filesPath, '/');
287
				$result = \OCA\Files_Trashbin\Trashbin::move2trash($filesPath, $ownerOnly);
288
				// in cross-storage cases the file will be copied
289
				// but not deleted, so we delete it here
290
				if ($result) {
291
					call_user_func_array([$this->storage, $method], [$path]);
292
				}
293
			} else {
294
				$result = call_user_func_array([$this->storage, $method], [$path]);
295
			}
296
			unset($this->deletedFiles[$normalized]);
297
		} else if ($this->storage->file_exists($path)) {
298
			$result = call_user_func_array([$this->storage, $method], [$path]);
299
		}
300
301
		return $result;
302
	}
303
304
	/**
305
	 * Setup the storate wrapper callback
306
	 */
307
	public static function setupStorage() {
308
		\OC\Files\Filesystem::addStorageWrapper('oc_trashbin', function ($mountPoint, $storage) {
309
			return new \OCA\Files_Trashbin\Storage(
310
				array('storage' => $storage, 'mountPoint' => $mountPoint),
311
				\OC::$server->getUserManager(),
312
				\OC::$server->getLogger(),
313
				\OC::$server->getEventDispatcher(),
314
				\OC::$server->getLazyRootFolder()
315
			);
316
		}, 1);
317
	}
318
319
}
320