Completed
Pull Request — stable8.2 (#27128)
by
unknown
18:34
created

View::testCacheIncompleteFolder()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 9

Duplication

Lines 13
Ratio 100 %

Importance

Changes 0
Metric Value
cc 1
eloc 9
nc 1
nop 0
dl 13
loc 13
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * Copyright (c) 2012 Robin Appelman <[email protected]>
4
 * This file is licensed under the Affero General Public License version 3 or
5
 * later.
6
 * See the COPYING-README file. */
7
8
namespace Test\Files;
9
10
use OC\Files\Cache\Watcher;
11
use OC\Files\Storage\Common;
12
use OC\Files\Mount\MountPoint;
13
use OC\Files\Storage\Temporary;
14
use OCP\Lock\ILockingProvider;
15
16
class TemporaryNoTouch extends \OC\Files\Storage\Temporary {
17
	public function touch($path, $mtime = null) {
18
		return false;
19
	}
20
}
21
22
class TemporaryNoCross extends \OC\Files\Storage\Temporary {
23
	public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
24
		return Common::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
25
	}
26
27
	public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
28
		return Common::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
29
	}
30
}
31
32
class TemporaryNoLocal extends \OC\Files\Storage\Temporary {
33
	public function instanceOfStorage($className) {
34
		if ($className === '\OC\Files\Storage\Local') {
35
			return false;
36
		} else {
37
			return parent::instanceOfStorage($className);
38
		}
39
	}
40
}
41
42
class View extends \Test\TestCase {
43
	/**
44
	 * @var \OC\Files\Storage\Storage[] $storages
45
	 */
46
	private $storages = array();
47
48
	/**
49
	 * @var string
50
	 */
51
	private $user;
52
53
	/**
54
	 * @var \OCP\IUser
55
	 */
56
	private $userObject;
57
58
	/**
59
	 * @var \OCP\IGroup
60
	 */
61
	private $groupObject;
62
63
	/** @var \OC\Files\Storage\Storage */
64
	private $tempStorage;
65
66
	protected function setUp() {
67
		parent::setUp();
68
		\OC_Hook::clear();
69
70
		\OC_User::clearBackends();
71
		\OC_User::useBackend(new \Test\Util\User\Dummy());
72
73
		//login
74
		$userManager = \OC::$server->getUserManager();
75
		$groupManager = \OC::$server->getGroupManager();
76
		$this->user = 'test';
77
		$this->userObject = $userManager->createUser('test', 'test');
0 ignored issues
show
Documentation Bug introduced by
It seems like $userManager->createUser('test', 'test') can also be of type false. However, the property $userObject is declared as type object<OCP\IUser>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
78
79
		$this->groupObject = $groupManager->createGroup('group1');
80
		$this->groupObject->addUser($this->userObject);
0 ignored issues
show
Security Bug introduced by
It seems like $this->userObject can also be of type false; however, OC\Group\Group::addUser() does only seem to accept object<OC\User\User>, did you maybe forget to handle an error condition?
Loading history...
81
82
		$this->loginAsUser($this->user);
83
		// clear mounts but somehow keep the root storage
84
		// that was initialized above...
85
		\OC\Files\Filesystem::clearMounts();
86
87
		$this->tempStorage = null;
88
	}
89
90
	protected function tearDown() {
91
		\OC_User::setUserId($this->user);
92
		foreach ($this->storages as $storage) {
93
			$cache = $storage->getCache();
94
			$ids = $cache->getAll();
0 ignored issues
show
Unused Code introduced by
$ids is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
95
			$cache->clear();
96
		}
97
98
		if ($this->tempStorage && !\OC_Util::runningOnWindows()) {
99
			system('rm -rf ' . escapeshellarg($this->tempStorage->getDataDir()));
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface OC\Files\Storage\Storage as the method getDataDir() does only exist in the following implementations of said interface: OC\Files\Storage\Temporary, Test\Files\Cache\LongId, Test\Files\Mount\LongId, Test\Files\Storage\CopyDirectoryStorage, Test\Files\Storage\StorageNoRecursiveCopy, Test\Files\TemporaryNoCross, Test\Files\TemporaryNoLocal, Test\Files\TemporaryNoTouch.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
100
		}
101
102
		$this->logout();
103
104
		$this->userObject->delete();
105
		$this->groupObject->delete();
106
107
		$mountProviderCollection = \OC::$server->getMountProviderCollection();
108
		\Test\TestCase::invokePrivate($mountProviderCollection, 'providers', [[]]);
109
110
		parent::tearDown();
111
	}
112
113
	/**
114
	 * @medium
115
	 */
116
	public function testCacheAPI() {
117
		$storage1 = $this->getTestStorage();
118
		$storage2 = $this->getTestStorage();
119
		$storage3 = $this->getTestStorage();
120
		$root = $this->getUniqueID('/');
121
		\OC\Files\Filesystem::mount($storage1, array(), $root . '/');
122
		\OC\Files\Filesystem::mount($storage2, array(), $root . '/substorage');
123
		\OC\Files\Filesystem::mount($storage3, array(), $root . '/folder/anotherstorage');
124
		$textSize = strlen("dummy file data\n");
125
		$imageSize = filesize(\OC::$SERVERROOT . '/core/img/logo.png');
126
		$storageSize = $textSize * 2 + $imageSize;
127
128
		$storageInfo = $storage3->getCache()->get('');
129
		$this->assertEquals($storageSize, $storageInfo['size']);
130
131
		$rootView = new \OC\Files\View($root);
132
133
		$cachedData = $rootView->getFileInfo('/foo.txt');
134
		$this->assertEquals($textSize, $cachedData['size']);
135
		$this->assertEquals('text/plain', $cachedData['mimetype']);
136
		$this->assertNotEquals(-1, $cachedData['permissions']);
137
138
		$cachedData = $rootView->getFileInfo('/');
139
		$this->assertEquals($storageSize * 3, $cachedData['size']);
140
		$this->assertEquals('httpd/unix-directory', $cachedData['mimetype']);
141
142
		// get cached data excluding mount points
143
		$cachedData = $rootView->getFileInfo('/', false);
144
		$this->assertEquals($storageSize, $cachedData['size']);
145
		$this->assertEquals('httpd/unix-directory', $cachedData['mimetype']);
146
147
		$cachedData = $rootView->getFileInfo('/folder');
148
		$this->assertEquals($storageSize + $textSize, $cachedData['size']);
149
		$this->assertEquals('httpd/unix-directory', $cachedData['mimetype']);
150
151
		$folderData = $rootView->getDirectoryContent('/');
152
		/**
153
		 * expected entries:
154
		 * folder
155
		 * foo.png
156
		 * foo.txt
157
		 * substorage
158
		 */
159
		$this->assertEquals(4, count($folderData));
160
		$this->assertEquals('folder', $folderData[0]['name']);
161
		$this->assertEquals('foo.png', $folderData[1]['name']);
162
		$this->assertEquals('foo.txt', $folderData[2]['name']);
163
		$this->assertEquals('substorage', $folderData[3]['name']);
164
165
		$this->assertEquals($storageSize + $textSize, $folderData[0]['size']);
166
		$this->assertEquals($imageSize, $folderData[1]['size']);
167
		$this->assertEquals($textSize, $folderData[2]['size']);
168
		$this->assertEquals($storageSize, $folderData[3]['size']);
169
170
		$folderData = $rootView->getDirectoryContent('/substorage');
171
		/**
172
		 * expected entries:
173
		 * folder
174
		 * foo.png
175
		 * foo.txt
176
		 */
177
		$this->assertEquals(3, count($folderData));
178
		$this->assertEquals('folder', $folderData[0]['name']);
179
		$this->assertEquals('foo.png', $folderData[1]['name']);
180
		$this->assertEquals('foo.txt', $folderData[2]['name']);
181
182
		$folderView = new \OC\Files\View($root . '/folder');
183
		$this->assertEquals($rootView->getFileInfo('/folder'), $folderView->getFileInfo('/'));
184
185
		$cachedData = $rootView->getFileInfo('/foo.txt');
186
		$this->assertFalse($cachedData['encrypted']);
187
		$id = $rootView->putFileInfo('/foo.txt', array('encrypted' => true));
188
		$cachedData = $rootView->getFileInfo('/foo.txt');
189
		$this->assertTrue($cachedData['encrypted']);
190
		$this->assertEquals($cachedData['fileid'], $id);
191
192
		$this->assertFalse($rootView->getFileInfo('/non/existing'));
193
		$this->assertEquals(array(), $rootView->getDirectoryContent('/non/existing'));
194
	}
195
196
	/**
197
	 * @medium
198
	 */
199
	public function testGetPath() {
200
		$storage1 = $this->getTestStorage();
201
		$storage2 = $this->getTestStorage();
202
		$storage3 = $this->getTestStorage();
203
		\OC\Files\Filesystem::mount($storage1, array(), '/');
204
		\OC\Files\Filesystem::mount($storage2, array(), '/substorage');
205
		\OC\Files\Filesystem::mount($storage3, array(), '/folder/anotherstorage');
206
207
		$rootView = new \OC\Files\View('');
208
209
		$cachedData = $rootView->getFileInfo('/foo.txt');
210
		$id1 = $cachedData['fileid'];
211
		$this->assertEquals('/foo.txt', $rootView->getPath($id1));
212
213
		$cachedData = $rootView->getFileInfo('/substorage/foo.txt');
214
		$id2 = $cachedData['fileid'];
215
		$this->assertEquals('/substorage/foo.txt', $rootView->getPath($id2));
216
217
		$folderView = new \OC\Files\View('/substorage');
218
		$this->assertEquals('/foo.txt', $folderView->getPath($id2));
219
		$this->assertNull($folderView->getPath($id1));
220
	}
221
222
	/**
223
	 * @medium
224
	 */
225 View Code Duplication
	public function testMountPointOverwrite() {
226
		$storage1 = $this->getTestStorage(false);
227
		$storage2 = $this->getTestStorage();
228
		$storage1->mkdir('substorage');
229
		\OC\Files\Filesystem::mount($storage1, array(), '/');
230
		\OC\Files\Filesystem::mount($storage2, array(), '/substorage');
231
232
		$rootView = new \OC\Files\View('');
233
		$folderContent = $rootView->getDirectoryContent('/');
234
		$this->assertEquals(4, count($folderContent));
235
	}
236
237
	public function sharingDisabledPermissionProvider() {
238
		return [
239
			['no', '', true],
240
			['yes', 'group1', false],
241
		];
242
	}
243
244
	/**
245
	 * @dataProvider sharingDisabledPermissionProvider
246
	 */
247
	public function testRemoveSharePermissionWhenSharingDisabledForUser($excludeGroups, $excludeGroupsList, $expectedShareable) {
248
		$appConfig = \OC::$server->getAppConfig();
249
		$oldExcludeGroupsFlag = $appConfig->getValue('core', 'shareapi_exclude_groups', 'no');
250
		$oldExcludeGroupsList = $appConfig->getValue('core', 'shareapi_exclude_groups_list', '');
251
		$appConfig->setValue('core', 'shareapi_exclude_groups', $excludeGroups);
252
		$appConfig->setValue('core', 'shareapi_exclude_groups_list', $excludeGroupsList);
253
254
		$storage1 = $this->getTestStorage();
255
		$storage2 = $this->getTestStorage();
256
		\OC\Files\Filesystem::mount($storage1, array(), '/');
257
		\OC\Files\Filesystem::mount($storage2, array(), '/mount');
258
259
		$view = new \OC\Files\View('/');
260
261
		$folderContent = $view->getDirectoryContent('');
262
		$this->assertEquals($expectedShareable, $folderContent[0]->isShareable());
263
264
		$folderContent = $view->getDirectoryContent('mount');
265
		$this->assertEquals($expectedShareable, $folderContent[0]->isShareable());
266
267
		$appConfig->setValue('core', 'shareapi_exclude_groups', $oldExcludeGroupsFlag);
268
		$appConfig->setValue('core', 'shareapi_exclude_groups_list', $oldExcludeGroupsList);
269
	}
270
271 View Code Duplication
	public function testCacheIncompleteFolder() {
272
		$storage1 = $this->getTestStorage(false);
273
		\OC\Files\Filesystem::clearMounts();
274
		\OC\Files\Filesystem::mount($storage1, array(), '/incomplete');
275
		$rootView = new \OC\Files\View('/incomplete');
276
277
		$entries = $rootView->getDirectoryContent('/');
278
		$this->assertEquals(3, count($entries));
279
280
		// /folder will already be in the cache but not scanned
281
		$entries = $rootView->getDirectoryContent('/folder');
282
		$this->assertEquals(1, count($entries));
283
	}
284
285
	public function testAutoScan() {
286
		$storage1 = $this->getTestStorage(false);
287
		$storage2 = $this->getTestStorage(false);
288
		\OC\Files\Filesystem::mount($storage1, array(), '/');
289
		\OC\Files\Filesystem::mount($storage2, array(), '/substorage');
290
		$textSize = strlen("dummy file data\n");
291
292
		$rootView = new \OC\Files\View('');
293
294
		$cachedData = $rootView->getFileInfo('/');
295
		$this->assertEquals('httpd/unix-directory', $cachedData['mimetype']);
296
		$this->assertEquals(-1, $cachedData['size']);
297
298
		$folderData = $rootView->getDirectoryContent('/substorage/folder');
299
		$this->assertEquals('text/plain', $folderData[0]['mimetype']);
300
		$this->assertEquals($textSize, $folderData[0]['size']);
301
	}
302
303
	/**
304
	 * @medium
305
	 */
306
	public function testSearch() {
307
		$storage1 = $this->getTestStorage();
308
		$storage2 = $this->getTestStorage();
309
		$storage3 = $this->getTestStorage();
310
		\OC\Files\Filesystem::mount($storage1, array(), '/');
311
		\OC\Files\Filesystem::mount($storage2, array(), '/substorage');
312
		\OC\Files\Filesystem::mount($storage3, array(), '/folder/anotherstorage');
313
314
		$rootView = new \OC\Files\View('');
315
316
		$results = $rootView->search('foo');
317
		$this->assertEquals(6, count($results));
318
		$paths = array();
319
		foreach ($results as $result) {
320
			$this->assertEquals($result['path'], \OC\Files\Filesystem::normalizePath($result['path']));
321
			$paths[] = $result['path'];
322
		}
323
		$this->assertContains('/foo.txt', $paths);
324
		$this->assertContains('/foo.png', $paths);
325
		$this->assertContains('/substorage/foo.txt', $paths);
326
		$this->assertContains('/substorage/foo.png', $paths);
327
		$this->assertContains('/folder/anotherstorage/foo.txt', $paths);
328
		$this->assertContains('/folder/anotherstorage/foo.png', $paths);
329
330
		$folderView = new \OC\Files\View('/folder');
331
		$results = $folderView->search('bar');
332
		$this->assertEquals(2, count($results));
333
		$paths = array();
334
		foreach ($results as $result) {
335
			$paths[] = $result['path'];
336
		}
337
		$this->assertContains('/anotherstorage/folder/bar.txt', $paths);
338
		$this->assertContains('/bar.txt', $paths);
339
340
		$results = $folderView->search('foo');
341
		$this->assertEquals(2, count($results));
342
		$paths = array();
343
		foreach ($results as $result) {
344
			$paths[] = $result['path'];
345
		}
346
		$this->assertContains('/anotherstorage/foo.txt', $paths);
347
		$this->assertContains('/anotherstorage/foo.png', $paths);
348
349
		$this->assertEquals(6, count($rootView->searchByMime('text')));
350
		$this->assertEquals(3, count($folderView->searchByMime('text')));
351
	}
352
353
	/**
354
	 * @medium
355
	 */
356
	public function testWatcher() {
357
		$storage1 = $this->getTestStorage();
358
		\OC\Files\Filesystem::mount($storage1, array(), '/');
359
		$storage1->getWatcher()->setPolicy(Watcher::CHECK_ALWAYS);
360
361
		$rootView = new \OC\Files\View('');
362
363
		$cachedData = $rootView->getFileInfo('foo.txt');
364
		$this->assertEquals(16, $cachedData['size']);
365
366
		$rootView->putFileInfo('foo.txt', array('storage_mtime' => 10));
367
		$storage1->file_put_contents('foo.txt', 'foo');
368
		clearstatcache();
369
370
		$cachedData = $rootView->getFileInfo('foo.txt');
371
		$this->assertEquals(3, $cachedData['size']);
372
	}
373
374
	/**
375
	 * @medium
376
	 */
377
	public function testCopyBetweenStorageNoCross() {
378
		$storage1 = $this->getTestStorage(true, '\Test\Files\TemporaryNoCross');
379
		$storage2 = $this->getTestStorage(true, '\Test\Files\TemporaryNoCross');
380
		$this->copyBetweenStorages($storage1, $storage2);
381
	}
382
383
	/**
384
	 * @medium
385
	 */
386
	public function testCopyBetweenStorageCross() {
387
		$storage1 = $this->getTestStorage();
388
		$storage2 = $this->getTestStorage();
389
		$this->copyBetweenStorages($storage1, $storage2);
390
	}
391
392
	/**
393
	 * @medium
394
	 */
395
	public function testCopyBetweenStorageCrossNonLocal() {
396
		$storage1 = $this->getTestStorage(true, '\Test\Files\TemporaryNoLocal');
397
		$storage2 = $this->getTestStorage(true, '\Test\Files\TemporaryNoLocal');
398
		$this->copyBetweenStorages($storage1, $storage2);
399
	}
400
401
	function copyBetweenStorages($storage1, $storage2) {
402
		\OC\Files\Filesystem::mount($storage1, array(), '/');
403
		\OC\Files\Filesystem::mount($storage2, array(), '/substorage');
404
405
		$rootView = new \OC\Files\View('');
406
		$rootView->mkdir('substorage/emptyfolder');
407
		$rootView->copy('substorage', 'anotherfolder');
408
		$this->assertTrue($rootView->is_dir('/anotherfolder'));
409
		$this->assertTrue($rootView->is_dir('/substorage'));
410
		$this->assertTrue($rootView->is_dir('/anotherfolder/emptyfolder'));
411
		$this->assertTrue($rootView->is_dir('/substorage/emptyfolder'));
412
		$this->assertTrue($rootView->file_exists('/anotherfolder/foo.txt'));
413
		$this->assertTrue($rootView->file_exists('/anotherfolder/foo.png'));
414
		$this->assertTrue($rootView->file_exists('/anotherfolder/folder/bar.txt'));
415
		$this->assertTrue($rootView->file_exists('/substorage/foo.txt'));
416
		$this->assertTrue($rootView->file_exists('/substorage/foo.png'));
417
		$this->assertTrue($rootView->file_exists('/substorage/folder/bar.txt'));
418
	}
419
420
	/**
421
	 * @medium
422
	 */
423
	public function testMoveBetweenStorageNoCross() {
424
		$storage1 = $this->getTestStorage(true, '\Test\Files\TemporaryNoCross');
425
		$storage2 = $this->getTestStorage(true, '\Test\Files\TemporaryNoCross');
426
		$this->moveBetweenStorages($storage1, $storage2);
427
	}
428
429
	/**
430
	 * @medium
431
	 */
432
	public function testMoveBetweenStorageCross() {
433
		$storage1 = $this->getTestStorage();
434
		$storage2 = $this->getTestStorage();
435
		$this->moveBetweenStorages($storage1, $storage2);
436
	}
437
438
	/**
439
	 * @medium
440
	 */
441
	public function testMoveBetweenStorageCrossNonLocal() {
442
		$storage1 = $this->getTestStorage(true, '\Test\Files\TemporaryNoLocal');
443
		$storage2 = $this->getTestStorage(true, '\Test\Files\TemporaryNoLocal');
444
		$this->moveBetweenStorages($storage1, $storage2);
445
	}
446
447 View Code Duplication
	function moveBetweenStorages($storage1, $storage2) {
448
		\OC\Files\Filesystem::mount($storage1, array(), '/');
449
		\OC\Files\Filesystem::mount($storage2, array(), '/substorage');
450
451
		$rootView = new \OC\Files\View('');
452
		$rootView->rename('foo.txt', 'substorage/folder/foo.txt');
453
		$this->assertFalse($rootView->file_exists('foo.txt'));
454
		$this->assertTrue($rootView->file_exists('substorage/folder/foo.txt'));
455
		$rootView->rename('substorage/folder', 'anotherfolder');
456
		$this->assertFalse($rootView->is_dir('substorage/folder'));
457
		$this->assertTrue($rootView->file_exists('anotherfolder/foo.txt'));
458
		$this->assertTrue($rootView->file_exists('anotherfolder/bar.txt'));
459
	}
460
461
	/**
462
	 * @medium
463
	 */
464
	public function testUnlink() {
465
		$storage1 = $this->getTestStorage();
466
		$storage2 = $this->getTestStorage();
467
		\OC\Files\Filesystem::mount($storage1, array(), '/');
468
		\OC\Files\Filesystem::mount($storage2, array(), '/substorage');
469
470
		$rootView = new \OC\Files\View('');
471
		$rootView->file_put_contents('/foo.txt', 'asd');
472
		$rootView->file_put_contents('/substorage/bar.txt', 'asd');
473
474
		$this->assertTrue($rootView->file_exists('foo.txt'));
475
		$this->assertTrue($rootView->file_exists('substorage/bar.txt'));
476
477
		$this->assertTrue($rootView->unlink('foo.txt'));
478
		$this->assertTrue($rootView->unlink('substorage/bar.txt'));
479
480
		$this->assertFalse($rootView->file_exists('foo.txt'));
481
		$this->assertFalse($rootView->file_exists('substorage/bar.txt'));
482
	}
483
484
	public function rmdirOrUnlinkDataProvider() {
485
		return [['rmdir'], ['unlink']];
486
	}
487
488
	/**
489
	 * @dataProvider rmdirOrUnlinkDataProvider
490
	 */
491
	public function testRmdir($method) {
492
		$storage1 = $this->getTestStorage();
493
		\OC\Files\Filesystem::mount($storage1, [], '/');
494
495
		$rootView = new \OC\Files\View('');
496
		$rootView->mkdir('sub');
497
		$rootView->mkdir('sub/deep');
498
		$rootView->file_put_contents('/sub/deep/foo.txt', 'asd');
499
500
		$this->assertTrue($rootView->file_exists('sub/deep/foo.txt'));
501
502
		$this->assertTrue($rootView->$method('sub'));
503
504
		$this->assertFalse($rootView->file_exists('sub'));
505
	}
506
507
	/**
508
	 * @medium
509
	 */
510 View Code Duplication
	public function testUnlinkRootMustFail() {
511
		$storage1 = $this->getTestStorage();
512
		$storage2 = $this->getTestStorage();
513
		\OC\Files\Filesystem::mount($storage1, array(), '/');
514
		\OC\Files\Filesystem::mount($storage2, array(), '/substorage');
515
516
		$rootView = new \OC\Files\View('');
517
		$rootView->file_put_contents('/foo.txt', 'asd');
518
		$rootView->file_put_contents('/substorage/bar.txt', 'asd');
519
520
		$this->assertFalse($rootView->unlink(''));
521
		$this->assertFalse($rootView->unlink('/'));
522
		$this->assertFalse($rootView->unlink('substorage'));
523
		$this->assertFalse($rootView->unlink('/substorage'));
524
	}
525
526
	/**
527
	 * @medium
528
	 */
529
	public function testTouch() {
530
		$storage = $this->getTestStorage(true, '\Test\Files\TemporaryNoTouch');
531
532
		\OC\Files\Filesystem::mount($storage, array(), '/');
533
534
		$rootView = new \OC\Files\View('');
535
		$oldCachedData = $rootView->getFileInfo('foo.txt');
536
537
		$rootView->touch('foo.txt', 500);
538
539
		$cachedData = $rootView->getFileInfo('foo.txt');
540
		$this->assertEquals(500, $cachedData['mtime']);
541
		$this->assertEquals($oldCachedData['storage_mtime'], $cachedData['storage_mtime']);
542
543
		$rootView->putFileInfo('foo.txt', array('storage_mtime' => 1000)); //make sure the watcher detects the change
544
		$rootView->file_put_contents('foo.txt', 'asd');
545
		$cachedData = $rootView->getFileInfo('foo.txt');
546
		$this->assertGreaterThanOrEqual($oldCachedData['mtime'], $cachedData['mtime']);
547
		$this->assertEquals($cachedData['storage_mtime'], $cachedData['mtime']);
548
	}
549
550
	/**
551
	 * @medium
552
	 */
553
	public function testViewHooks() {
554
		$storage1 = $this->getTestStorage();
555
		$storage2 = $this->getTestStorage();
556
		$defaultRoot = \OC\Files\Filesystem::getRoot();
557
		\OC\Files\Filesystem::mount($storage1, array(), '/');
558
		\OC\Files\Filesystem::mount($storage2, array(), $defaultRoot . '/substorage');
559
		\OC_Hook::connect('OC_Filesystem', 'post_write', $this, 'dummyHook');
560
561
		$rootView = new \OC\Files\View('');
562
		$subView = new \OC\Files\View($defaultRoot . '/substorage');
563
		$this->hookPath = null;
564
565
		$rootView->file_put_contents('/foo.txt', 'asd');
566
		$this->assertNull($this->hookPath);
567
568
		$subView->file_put_contents('/foo.txt', 'asd');
569
		$this->assertEquals('/substorage/foo.txt', $this->hookPath);
570
	}
571
572
	private $hookPath;
573
574
	public function dummyHook($params) {
575
		$this->hookPath = $params['path'];
576
	}
577
578
	public function testSearchNotOutsideView() {
579
		$storage1 = $this->getTestStorage();
580
		\OC\Files\Filesystem::mount($storage1, array(), '/');
581
		$storage1->rename('folder', 'foo');
582
		$scanner = $storage1->getScanner();
583
		$scanner->scan('');
584
585
		$view = new \OC\Files\View('/foo');
586
587
		$result = $view->search('.txt');
588
		$this->assertCount(1, $result);
589
	}
590
591
	/**
592
	 * @param bool $scan
593
	 * @param string $class
594
	 * @return \OC\Files\Storage\Storage
595
	 */
596 View Code Duplication
	private function getTestStorage($scan = true, $class = '\OC\Files\Storage\Temporary') {
597
		/**
598
		 * @var \OC\Files\Storage\Storage $storage
599
		 */
600
		$storage = new $class(array());
601
		$textData = "dummy file data\n";
602
		$imgData = file_get_contents(\OC::$SERVERROOT . '/core/img/logo.png');
603
		$storage->mkdir('folder');
604
		$storage->file_put_contents('foo.txt', $textData);
605
		$storage->file_put_contents('foo.png', $imgData);
606
		$storage->file_put_contents('folder/bar.txt', $textData);
607
608
		if ($scan) {
609
			$scanner = $storage->getScanner();
610
			$scanner->scan('');
611
		}
612
		$this->storages[] = $storage;
613
		return $storage;
614
	}
615
616
	/**
617
	 * @medium
618
	 */
619
	public function testViewHooksIfRootStartsTheSame() {
620
		$storage1 = $this->getTestStorage();
621
		$storage2 = $this->getTestStorage();
622
		$defaultRoot = \OC\Files\Filesystem::getRoot();
623
		\OC\Files\Filesystem::mount($storage1, array(), '/');
624
		\OC\Files\Filesystem::mount($storage2, array(), $defaultRoot . '_substorage');
625
		\OC_Hook::connect('OC_Filesystem', 'post_write', $this, 'dummyHook');
626
627
		$subView = new \OC\Files\View($defaultRoot . '_substorage');
628
		$this->hookPath = null;
629
630
		$subView->file_put_contents('/foo.txt', 'asd');
631
		$this->assertNull($this->hookPath);
632
	}
633
634
	private $hookWritePath;
635
	private $hookCreatePath;
636
	private $hookUpdatePath;
637
638
	public function dummyHookWrite($params) {
639
		$this->hookWritePath = $params['path'];
640
	}
641
642
	public function dummyHookUpdate($params) {
643
		$this->hookUpdatePath = $params['path'];
644
	}
645
646
	public function dummyHookCreate($params) {
647
		$this->hookCreatePath = $params['path'];
648
	}
649
650
	public function testEditNoCreateHook() {
651
		$storage1 = $this->getTestStorage();
652
		$storage2 = $this->getTestStorage();
653
		$defaultRoot = \OC\Files\Filesystem::getRoot();
654
		\OC\Files\Filesystem::mount($storage1, array(), '/');
655
		\OC\Files\Filesystem::mount($storage2, array(), $defaultRoot);
656
		\OC_Hook::connect('OC_Filesystem', 'post_create', $this, 'dummyHookCreate');
657
		\OC_Hook::connect('OC_Filesystem', 'post_update', $this, 'dummyHookUpdate');
658
		\OC_Hook::connect('OC_Filesystem', 'post_write', $this, 'dummyHookWrite');
659
660
		$view = new \OC\Files\View($defaultRoot);
661
		$this->hookWritePath = $this->hookUpdatePath = $this->hookCreatePath = null;
662
663
		$view->file_put_contents('/asd.txt', 'foo');
664
		$this->assertEquals('/asd.txt', $this->hookCreatePath);
665
		$this->assertNull($this->hookUpdatePath);
666
		$this->assertEquals('/asd.txt', $this->hookWritePath);
667
668
		$this->hookWritePath = $this->hookUpdatePath = $this->hookCreatePath = null;
669
670
		$view->file_put_contents('/asd.txt', 'foo');
671
		$this->assertNull($this->hookCreatePath);
672
		$this->assertEquals('/asd.txt', $this->hookUpdatePath);
673
		$this->assertEquals('/asd.txt', $this->hookWritePath);
674
675
		\OC_Hook::clear('OC_Filesystem', 'post_create');
676
		\OC_Hook::clear('OC_Filesystem', 'post_update');
677
		\OC_Hook::clear('OC_Filesystem', 'post_write');
678
	}
679
680
	/**
681
	 * @dataProvider resolvePathTestProvider
682
	 */
683
	public function testResolvePath($expected, $pathToTest) {
684
		$storage1 = $this->getTestStorage();
685
		\OC\Files\Filesystem::mount($storage1, array(), '/');
686
687
		$view = new \OC\Files\View('');
688
689
		$result = $view->resolvePath($pathToTest);
690
		$this->assertEquals($expected, $result[1]);
691
692
		$exists = $view->file_exists($pathToTest);
693
		$this->assertTrue($exists);
694
695
		$exists = $view->file_exists($result[1]);
696
		$this->assertTrue($exists);
697
	}
698
699
	function resolvePathTestProvider() {
700
		return array(
701
			array('foo.txt', 'foo.txt'),
702
			array('foo.txt', '/foo.txt'),
703
			array('folder', 'folder'),
704
			array('folder', '/folder'),
705
			array('folder', 'folder/'),
706
			array('folder', '/folder/'),
707
			array('folder/bar.txt', 'folder/bar.txt'),
708
			array('folder/bar.txt', '/folder/bar.txt'),
709
			array('', ''),
710
			array('', '/'),
711
		);
712
	}
713
714
	public function testUTF8Names() {
715
		$names = array('虚', '和知しゃ和で', 'regular ascii', 'sɨˈrɪlɪk', 'ѨѬ', 'أنا أحب القراءة كثيرا');
716
717
		$storage = new \OC\Files\Storage\Temporary(array());
718
		\OC\Files\Filesystem::mount($storage, array(), '/');
719
720
		$rootView = new \OC\Files\View('');
721
		foreach ($names as $name) {
722
			$rootView->file_put_contents('/' . $name, 'dummy content');
723
		}
724
725
		$list = $rootView->getDirectoryContent('/');
726
727
		$this->assertCount(count($names), $list);
728
		foreach ($list as $item) {
729
			$this->assertContains($item['name'], $names);
730
		}
731
732
		$cache = $storage->getCache();
733
		$scanner = $storage->getScanner();
734
		$scanner->scan('');
735
736
		$list = $cache->getFolderContents('');
737
738
		$this->assertCount(count($names), $list);
739
		foreach ($list as $item) {
740
			$this->assertContains($item['name'], $names);
741
		}
742
	}
743
744
	public function xtestLongPath() {
745
746
		$storage = new \OC\Files\Storage\Temporary(array());
747
		\OC\Files\Filesystem::mount($storage, array(), '/');
748
749
		$rootView = new \OC\Files\View('');
750
751
		$longPath = '';
752
		$ds = DIRECTORY_SEPARATOR;
753
		/*
754
		 * 4096 is the maximum path length in file_cache.path in *nix
755
		 * 1024 is the max path length in mac
756
		 * 228 is the max path length in windows
757
		 */
758
		$folderName = 'abcdefghijklmnopqrstuvwxyz012345678901234567890123456789';
759
		$tmpdirLength = strlen(\OC_Helper::tmpFolder());
760
		if (\OC_Util::runningOnWindows()) {
761
			$this->markTestSkipped('[Windows] ');
762
			$depth = ((260 - $tmpdirLength) / 57);
763
		} elseif (\OC_Util::runningOnMac()) {
764
			$depth = ((1024 - $tmpdirLength) / 57);
765
		} else {
766
			$depth = ((4000 - $tmpdirLength) / 57);
767
		}
768
		foreach (range(0, $depth - 1) as $i) {
769
			$longPath .= $ds . $folderName;
770
			$result = $rootView->mkdir($longPath);
771
			$this->assertTrue($result, "mkdir failed on $i - path length: " . strlen($longPath));
772
773
			$result = $rootView->file_put_contents($longPath . "{$ds}test.txt", 'lorem');
774
			$this->assertEquals(5, $result, "file_put_contents failed on $i");
775
776
			$this->assertTrue($rootView->file_exists($longPath));
777
			$this->assertTrue($rootView->file_exists($longPath . "{$ds}test.txt"));
778
		}
779
780
		$cache = $storage->getCache();
781
		$scanner = $storage->getScanner();
782
		$scanner->scan('');
783
784
		$longPath = $folderName;
785
		foreach (range(0, $depth - 1) as $i) {
786
			$cachedFolder = $cache->get($longPath);
787
			$this->assertTrue(is_array($cachedFolder), "No cache entry for folder at $i");
788
			$this->assertEquals($folderName, $cachedFolder['name'], "Wrong cache entry for folder at $i");
789
790
			$cachedFile = $cache->get($longPath . '/test.txt');
791
			$this->assertTrue(is_array($cachedFile), "No cache entry for file at $i");
792
			$this->assertEquals('test.txt', $cachedFile['name'], "Wrong cache entry for file at $i");
793
794
			$longPath .= $ds . $folderName;
795
		}
796
	}
797
798
	public function testTouchNotSupported() {
799
		$storage = new TemporaryNoTouch(array());
800
		$scanner = $storage->getScanner();
801
		\OC\Files\Filesystem::mount($storage, array(), '/test/');
802
		$past = time() - 100;
803
		$storage->file_put_contents('test', 'foobar');
804
		$scanner->scan('');
805
		$view = new \OC\Files\View('');
806
		$info = $view->getFileInfo('/test/test');
807
808
		$view->touch('/test/test', $past);
809
		$scanner->scanFile('test', \OC\Files\Cache\Scanner::REUSE_ETAG);
810
811
		$info2 = $view->getFileInfo('/test/test');
812
		$this->assertSame($info['etag'], $info2['etag']);
813
	}
814
815
	public function testWatcherEtagCrossStorage() {
816
		$storage1 = new Temporary(array());
817
		$storage2 = new Temporary(array());
818
		$scanner1 = $storage1->getScanner();
819
		$scanner2 = $storage2->getScanner();
820
		$storage1->mkdir('sub');
821
		\OC\Files\Filesystem::mount($storage1, array(), '/test/');
822
		\OC\Files\Filesystem::mount($storage2, array(), '/test/sub/storage');
823
824
		$past = time() - 100;
825
		$storage2->file_put_contents('test.txt', 'foobar');
826
		$scanner1->scan('');
827
		$scanner2->scan('');
828
		$view = new \OC\Files\View('');
829
830
		$storage2->getWatcher('')->setPolicy(Watcher::CHECK_ALWAYS);
831
832
		$oldFileInfo = $view->getFileInfo('/test/sub/storage/test.txt');
833
		$oldFolderInfo = $view->getFileInfo('/test');
834
835
		$storage2->getCache()->update($oldFileInfo->getId(), array(
836
			'storage_mtime' => $past
837
		));
838
839
		$view->getFileInfo('/test/sub/storage/test.txt');
840
		$newFolderInfo = $view->getFileInfo('/test');
841
842
		$this->assertNotEquals($newFolderInfo->getEtag(), $oldFolderInfo->getEtag());
843
	}
844
845
	/**
846
	 * @dataProvider absolutePathProvider
847
	 */
848
	public function testGetAbsolutePath($expectedPath, $relativePath) {
849
		$view = new \OC\Files\View('/files');
850
		$this->assertEquals($expectedPath, $view->getAbsolutePath($relativePath));
851
	}
852
853
	public function testPartFileInfo() {
854
		$storage = new Temporary(array());
855
		$scanner = $storage->getScanner();
856
		\OC\Files\Filesystem::mount($storage, array(), '/test/');
857
		$storage->file_put_contents('test.part', 'foobar');
858
		$scanner->scan('');
859
		$view = new \OC\Files\View('/test');
860
		$info = $view->getFileInfo('test.part');
861
862
		$this->assertInstanceOf('\OCP\Files\FileInfo', $info);
863
		$this->assertNull($info->getId());
864
		$this->assertEquals(6, $info->getSize());
865
	}
866
867
	function absolutePathProvider() {
868
		return array(
869
			array('/files/', ''),
870
			array('/files/0', '0'),
871
			array('/files/false', 'false'),
872
			array('/files/true', 'true'),
873
			array('/files/', '/'),
874
			array('/files/test', 'test'),
875
			array('/files/test', '/test'),
876
		);
877
	}
878
879
	/**
880
	 * @dataProvider chrootRelativePathProvider
881
	 */
882
	function testChrootGetRelativePath($root, $absolutePath, $expectedPath) {
883
		$view = new \OC\Files\View('/files');
884
		$view->chroot($root);
885
		$this->assertEquals($expectedPath, $view->getRelativePath($absolutePath));
886
	}
887
888
	public function chrootRelativePathProvider() {
889
		return $this->relativePathProvider('/');
890
	}
891
892
	/**
893
	 * @dataProvider initRelativePathProvider
894
	 */
895
	public function testInitGetRelativePath($root, $absolutePath, $expectedPath) {
896
		$view = new \OC\Files\View($root);
897
		$this->assertEquals($expectedPath, $view->getRelativePath($absolutePath));
898
	}
899
900
	public function initRelativePathProvider() {
901
		return $this->relativePathProvider(null);
902
	}
903
904
	public function relativePathProvider($missingRootExpectedPath) {
905
		return array(
906
			// No root - returns the path
907
			array('', '/files', '/files'),
908
			array('', '/files/', '/files/'),
909
910
			// Root equals path - /
911
			array('/files/', '/files/', '/'),
912
			array('/files/', '/files', '/'),
913
			array('/files', '/files/', '/'),
914
			array('/files', '/files', '/'),
915
916
			// False negatives: chroot fixes those by adding the leading slash.
917
			// But setting them up with this root (instead of chroot($root))
918
			// will fail them, although they should be the same.
919
			// TODO init should be fixed, so it also adds the leading slash
920
			array('files/', '/files/', $missingRootExpectedPath),
921
			array('files', '/files/', $missingRootExpectedPath),
922
			array('files/', '/files', $missingRootExpectedPath),
923
			array('files', '/files', $missingRootExpectedPath),
924
925
			// False negatives: Paths provided to the method should have a leading slash
926
			// TODO input should be checked to have a leading slash
927
			array('/files/', 'files/', null),
928
			array('/files', 'files/', null),
929
			array('/files/', 'files', null),
930
			array('/files', 'files', null),
931
932
			// with trailing slashes
933
			array('/files/', '/files/0', '0'),
934
			array('/files/', '/files/false', 'false'),
935
			array('/files/', '/files/true', 'true'),
936
			array('/files/', '/files/test', 'test'),
937
			array('/files/', '/files/test/foo', 'test/foo'),
938
939
			// without trailing slashes
940
			// TODO false expectation: Should match "with trailing slashes"
941
			array('/files', '/files/0', '/0'),
942
			array('/files', '/files/false', '/false'),
943
			array('/files', '/files/true', '/true'),
944
			array('/files', '/files/test', '/test'),
945
			array('/files', '/files/test/foo', '/test/foo'),
946
947
			// leading slashes
948
			array('/files/', '/files_trashbin/', null),
949
			array('/files', '/files_trashbin/', null),
950
			array('/files/', '/files_trashbin', null),
951
			array('/files', '/files_trashbin', null),
952
953
			// no leading slashes
954
			array('files/', 'files_trashbin/', null),
955
			array('files', 'files_trashbin/', null),
956
			array('files/', 'files_trashbin', null),
957
			array('files', 'files_trashbin', null),
958
959
			// mixed leading slashes
960
			array('files/', '/files_trashbin/', null),
961
			array('/files/', 'files_trashbin/', null),
962
			array('files', '/files_trashbin/', null),
963
			array('/files', 'files_trashbin/', null),
964
			array('files/', '/files_trashbin', null),
965
			array('/files/', 'files_trashbin', null),
966
			array('files', '/files_trashbin', null),
967
			array('/files', 'files_trashbin', null),
968
969
			array('files', 'files_trashbin/test', null),
970
			array('/files', '/files_trashbin/test', null),
971
			array('/files', 'files_trashbin/test', null),
972
		);
973
	}
974
975
	public function testFileView() {
976
		$storage = new Temporary(array());
977
		$scanner = $storage->getScanner();
978
		$storage->file_put_contents('foo.txt', 'bar');
979
		\OC\Files\Filesystem::mount($storage, array(), '/test/');
980
		$scanner->scan('');
981
		$view = new \OC\Files\View('/test/foo.txt');
982
983
		$this->assertEquals('bar', $view->file_get_contents(''));
984
		$fh = tmpfile();
985
		fwrite($fh, 'foo');
986
		rewind($fh);
987
		$view->file_put_contents('', $fh);
988
		$this->assertEquals('foo', $view->file_get_contents(''));
989
	}
990
991
	/**
992
	 * @dataProvider tooLongPathDataProvider
993
	 * @expectedException \OCP\Files\InvalidPathException
994
	 */
995
	public function testTooLongPath($operation, $param0 = null) {
996
997
		$longPath = '';
998
		// 4000 is the maximum path length in file_cache.path
999
		$folderName = 'abcdefghijklmnopqrstuvwxyz012345678901234567890123456789';
1000
		$depth = (4000 / 57);
1001
		foreach (range(0, $depth + 1) as $i) {
1002
			$longPath .= '/' . $folderName;
1003
		}
1004
1005
		$storage = new \OC\Files\Storage\Temporary(array());
1006
		$this->tempStorage = $storage; // for later hard cleanup
1007
		\OC\Files\Filesystem::mount($storage, array(), '/');
1008
1009
		$rootView = new \OC\Files\View('');
1010
1011
		if ($param0 === '@0') {
1012
			$param0 = $longPath;
1013
		}
1014
1015
		if ($operation === 'hash') {
1016
			$param0 = $longPath;
1017
			$longPath = 'md5';
1018
		}
1019
1020
		call_user_func(array($rootView, $operation), $longPath, $param0);
1021
	}
1022
1023
	public function tooLongPathDataProvider() {
1024
		return array(
1025
			array('getAbsolutePath'),
1026
			array('getRelativePath'),
1027
			array('getMountPoint'),
1028
			array('resolvePath'),
1029
			array('getLocalFile'),
1030
			array('getLocalFolder'),
1031
			array('mkdir'),
1032
			array('rmdir'),
1033
			array('opendir'),
1034
			array('is_dir'),
1035
			array('is_file'),
1036
			array('stat'),
1037
			array('filetype'),
1038
			array('filesize'),
1039
			array('readfile'),
1040
			array('isCreatable'),
1041
			array('isReadable'),
1042
			array('isUpdatable'),
1043
			array('isDeletable'),
1044
			array('isSharable'),
1045
			array('file_exists'),
1046
			array('filemtime'),
1047
			array('touch'),
1048
			array('file_get_contents'),
1049
			array('unlink'),
1050
			array('deleteAll'),
1051
			array('toTmpFile'),
1052
			array('getMimeType'),
1053
			array('free_space'),
1054
			array('getFileInfo'),
1055
			array('getDirectoryContent'),
1056
			array('getOwner'),
1057
			array('getETag'),
1058
			array('file_put_contents', 'ipsum'),
1059
			array('rename', '@0'),
1060
			array('copy', '@0'),
1061
			array('fopen', 'r'),
1062
			array('fromTmpFile', '@0'),
1063
			array('hash'),
1064
			array('hasUpdated', 0),
1065
			array('putFileInfo', array()),
1066
		);
1067
	}
1068
1069
	public function testRenameCrossStoragePreserveMtime() {
1070
		$storage1 = new Temporary(array());
1071
		$storage2 = new Temporary(array());
1072
		$scanner1 = $storage1->getScanner();
0 ignored issues
show
Unused Code introduced by
$scanner1 is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1073
		$scanner2 = $storage2->getScanner();
0 ignored issues
show
Unused Code introduced by
$scanner2 is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1074
		$storage1->mkdir('sub');
1075
		$storage1->mkdir('foo');
1076
		$storage1->file_put_contents('foo.txt', 'asd');
1077
		$storage1->file_put_contents('foo/bar.txt', 'asd');
1078
		\OC\Files\Filesystem::mount($storage1, array(), '/test/');
1079
		\OC\Files\Filesystem::mount($storage2, array(), '/test/sub/storage');
1080
1081
		$view = new \OC\Files\View('');
1082
		$time = time() - 200;
1083
		$view->touch('/test/foo.txt', $time);
1084
		$view->touch('/test/foo', $time);
1085
		$view->touch('/test/foo/bar.txt', $time);
1086
1087
		$view->rename('/test/foo.txt', '/test/sub/storage/foo.txt');
1088
1089
		$this->assertEquals($time, $view->filemtime('/test/sub/storage/foo.txt'));
1090
1091
		$view->rename('/test/foo', '/test/sub/storage/foo');
1092
1093
		$this->assertEquals($time, $view->filemtime('/test/sub/storage/foo/bar.txt'));
1094
	}
1095
1096
	public function testRenameFailDeleteTargetKeepSource() {
1097
		$this->doTestCopyRenameFail('rename');
1098
	}
1099
1100
	public function testCopyFailDeleteTargetKeepSource() {
1101
		$this->doTestCopyRenameFail('copy');
1102
	}
1103
1104
	private function doTestCopyRenameFail($operation) {
1105
		$storage1 = new Temporary(array());
1106
		/** @var \PHPUnit_Framework_MockObject_MockObject | \OC\Files\Storage\Temporary $storage2 */
1107
		$storage2 = $this->getMockBuilder('\Test\Files\TemporaryNoCross')
1108
			->setConstructorArgs([[]])
1109
			->setMethods(['fopen'])
1110
			->getMock();
1111
1112
		$storage2->expects($this->any())
1113
			->method('fopen')
1114
			->will($this->returnCallback(function ($path, $mode) use ($storage2) {
1115
				/** @var \PHPUnit_Framework_MockObject_MockObject | \OC\Files\Storage\Temporary $storage2 */
1116
				$source = fopen($storage2->getSourcePath($path), $mode);
1117
				return \OC\Files\Stream\Quota::wrap($source, 9);
1118
			}));
1119
1120
		$storage1->mkdir('sub');
1121
		$storage1->file_put_contents('foo.txt', '0123456789ABCDEFGH');
1122
		$storage1->mkdir('dirtomove');
1123
		$storage1->file_put_contents('dirtomove/indir1.txt', '0123456'); // fits
1124
		$storage1->file_put_contents('dirtomove/indir2.txt', '0123456789ABCDEFGH'); // doesn't fit
1125
		$storage2->file_put_contents('existing.txt', '0123');
1126
		$storage1->getScanner()->scan('');
1127
		$storage2->getScanner()->scan('');
1128
		\OC\Files\Filesystem::mount($storage1, array(), '/test/');
1129
		\OC\Files\Filesystem::mount($storage2, array(), '/test/sub/storage');
1130
1131
		// move file
1132
		$view = new \OC\Files\View('');
1133
		$this->assertTrue($storage1->file_exists('foo.txt'));
1134
		$this->assertFalse($storage2->file_exists('foo.txt'));
1135
		$this->assertFalse($view->$operation('/test/foo.txt', '/test/sub/storage/foo.txt'));
1136
		$this->assertFalse($storage2->file_exists('foo.txt'));
1137
		$this->assertFalse($storage2->getCache()->get('foo.txt'));
1138
		$this->assertTrue($storage1->file_exists('foo.txt'));
1139
1140
		// if target exists, it will be deleted too
1141
		$this->assertFalse($view->$operation('/test/foo.txt', '/test/sub/storage/existing.txt'));
1142
		$this->assertFalse($storage2->file_exists('existing.txt'));
1143
		$this->assertFalse($storage2->getCache()->get('existing.txt'));
1144
		$this->assertTrue($storage1->file_exists('foo.txt'));
1145
1146
		// move folder
1147
		$this->assertFalse($view->$operation('/test/dirtomove/', '/test/sub/storage/dirtomove/'));
1148
		// since the move failed, the full source tree is kept
1149
		$this->assertTrue($storage1->file_exists('dirtomove/indir1.txt'));
1150
		$this->assertTrue($storage1->file_exists('dirtomove/indir2.txt'));
1151
		// second file not moved/copied
1152
		$this->assertFalse($storage2->file_exists('dirtomove/indir2.txt'));
1153
		$this->assertFalse($storage2->getCache()->get('dirtomove/indir2.txt'));
1154
1155
	}
1156
1157
	public function testDeleteFailKeepCache() {
1158
		/**
1159
		 * @var \PHPUnit_Framework_MockObject_MockObject | \OC\Files\Storage\Temporary $storage
1160
		 */
1161
		$storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
1162
			->setConstructorArgs(array(array()))
1163
			->setMethods(array('unlink'))
1164
			->getMock();
1165
		$storage->expects($this->once())
1166
			->method('unlink')
1167
			->will($this->returnValue(false));
1168
		$scanner = $storage->getScanner();
1169
		$cache = $storage->getCache();
1170
		$storage->file_put_contents('foo.txt', 'asd');
1171
		$scanner->scan('');
1172
		\OC\Files\Filesystem::mount($storage, array(), '/test/');
1173
1174
		$view = new \OC\Files\View('/test');
1175
1176
		$this->assertFalse($view->unlink('foo.txt'));
1177
		$this->assertTrue($cache->inCache('foo.txt'));
1178
	}
1179
1180
	function directoryTraversalProvider() {
1181
		return [
1182
			['../test/'],
1183
			['..\\test\\my/../folder'],
1184
			['/test/my/../foo\\'],
1185
		];
1186
	}
1187
1188
	/**
1189
	 * @dataProvider directoryTraversalProvider
1190
	 * @expectedException \Exception
1191
	 * @param string $root
1192
	 */
1193
	public function testConstructDirectoryTraversalException($root) {
1194
		new \OC\Files\View($root);
1195
	}
1196
1197
	public function testRenameOverWrite() {
1198
		$storage = new Temporary(array());
1199
		$scanner = $storage->getScanner();
1200
		$storage->mkdir('sub');
1201
		$storage->mkdir('foo');
1202
		$storage->file_put_contents('foo.txt', 'asd');
1203
		$storage->file_put_contents('foo/bar.txt', 'asd');
1204
		$scanner->scan('');
1205
		\OC\Files\Filesystem::mount($storage, array(), '/test/');
1206
		$view = new \OC\Files\View('');
1207
		$this->assertTrue($view->rename('/test/foo.txt', '/test/foo/bar.txt'));
1208
	}
1209
1210
	public function testSetMountOptionsInStorage() {
1211
		$mount = new MountPoint('\OC\Files\Storage\Temporary', '/asd/', [[]], \OC\Files\Filesystem::getLoader(), ['foo' => 'bar']);
1212
		\OC\Files\Filesystem::getMountManager()->addMount($mount);
1213
		/** @var \OC\Files\Storage\Common $storage */
1214
		$storage = $mount->getStorage();
1215
		$this->assertEquals($storage->getMountOption('foo'), 'bar');
1216
	}
1217
1218
	public function testSetMountOptionsWatcherPolicy() {
1219
		$mount = new MountPoint('\OC\Files\Storage\Temporary', '/asd/', [[]], \OC\Files\Filesystem::getLoader(), ['filesystem_check_changes' => Watcher::CHECK_NEVER]);
1220
		\OC\Files\Filesystem::getMountManager()->addMount($mount);
1221
		/** @var \OC\Files\Storage\Common $storage */
1222
		$storage = $mount->getStorage();
1223
		$watcher = $storage->getWatcher();
1224
		$this->assertEquals(Watcher::CHECK_NEVER, $watcher->getPolicy());
1225
	}
1226
1227
	public function testGetAbsolutePathOnNull() {
1228
		$view = new \OC\Files\View();
1229
		$this->assertNull($view->getAbsolutePath(null));
1230
	}
1231
1232
	public function testGetRelativePathOnNull() {
1233
		$view = new \OC\Files\View();
1234
		$this->assertNull($view->getRelativePath(null));
1235
	}
1236
1237
	/**
1238
	 * @expectedException \InvalidArgumentException
1239
	 */
1240
	public function testNullAsRoot() {
1241
		new \OC\Files\View(null);
1242
	}
1243
1244
	/**
1245
	 * e.g. reading from a folder that's being renamed
1246
	 *
1247
	 * @expectedException \OCP\Lock\LockedException
1248
	 *
1249
	 * @dataProvider dataLockPaths
1250
	 *
1251
	 * @param string $rootPath
1252
	 * @param string $pathPrefix
1253
	 */
1254 View Code Duplication
	public function testReadFromWriteLockedPath($rootPath, $pathPrefix) {
1255
		$rootPath = str_replace('{folder}', 'files', $rootPath);
1256
		$pathPrefix = str_replace('{folder}', 'files', $pathPrefix);
1257
1258
		$view = new \OC\Files\View($rootPath);
1259
		$storage = new Temporary(array());
1260
		\OC\Files\Filesystem::mount($storage, [], '/');
1261
		$this->assertTrue($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_EXCLUSIVE));
1262
		$view->lockFile($pathPrefix . '/foo/bar/asd', ILockingProvider::LOCK_SHARED);
1263
	}
1264
1265
	/**
1266
	 * Reading from a files_encryption folder that's being renamed
1267
	 *
1268
	 * @dataProvider dataLockPaths
1269
	 *
1270
	 * @param string $rootPath
1271
	 * @param string $pathPrefix
1272
	 */
1273 View Code Duplication
	public function testReadFromWriteUnlockablePath($rootPath, $pathPrefix) {
1274
		$rootPath = str_replace('{folder}', 'files_encryption', $rootPath);
1275
		$pathPrefix = str_replace('{folder}', 'files_encryption', $pathPrefix);
1276
1277
		$view = new \OC\Files\View($rootPath);
1278
		$storage = new Temporary(array());
1279
		\OC\Files\Filesystem::mount($storage, [], '/');
1280
		$this->assertFalse($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_EXCLUSIVE));
1281
		$this->assertFalse($view->lockFile($pathPrefix . '/foo/bar/asd', ILockingProvider::LOCK_SHARED));
1282
	}
1283
1284
	/**
1285
	 * e.g. writing a file that's being downloaded
1286
	 *
1287
	 * @expectedException \OCP\Lock\LockedException
1288
	 *
1289
	 * @dataProvider dataLockPaths
1290
	 *
1291
	 * @param string $rootPath
1292
	 * @param string $pathPrefix
1293
	 */
1294 View Code Duplication
	public function testWriteToReadLockedFile($rootPath, $pathPrefix) {
1295
		$rootPath = str_replace('{folder}', 'files', $rootPath);
1296
		$pathPrefix = str_replace('{folder}', 'files', $pathPrefix);
1297
1298
		$view = new \OC\Files\View($rootPath);
1299
		$storage = new Temporary(array());
1300
		\OC\Files\Filesystem::mount($storage, [], '/');
1301
		$this->assertTrue($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_SHARED));
1302
		$view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_EXCLUSIVE);
1303
	}
1304
1305
	/**
1306
	 * Writing a file that's being downloaded
1307
	 *
1308
	 * @dataProvider dataLockPaths
1309
	 *
1310
	 * @param string $rootPath
1311
	 * @param string $pathPrefix
1312
	 */
1313 View Code Duplication
	public function testWriteToReadUnlockableFile($rootPath, $pathPrefix) {
1314
		$rootPath = str_replace('{folder}', 'files_encryption', $rootPath);
1315
		$pathPrefix = str_replace('{folder}', 'files_encryption', $pathPrefix);
1316
1317
		$view = new \OC\Files\View($rootPath);
1318
		$storage = new Temporary(array());
1319
		\OC\Files\Filesystem::mount($storage, [], '/');
1320
		$this->assertFalse($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_SHARED));
1321
		$this->assertFalse($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_EXCLUSIVE));
1322
	}
1323
1324
	/**
1325
	 * Test that locks are on mount point paths instead of mount root
1326
	 */
1327 View Code Duplication
	public function testLockLocalMountPointPathInsteadOfStorageRoot() {
1328
		$lockingProvider = \OC::$server->getLockingProvider();
1329
		$view = new \OC\Files\View('/testuser/files/');
1330
		$storage = new Temporary([]);
1331
		\OC\Files\Filesystem::mount($storage, [], '/');
1332
		$mountedStorage = new Temporary([]);
1333
		\OC\Files\Filesystem::mount($mountedStorage, [], '/testuser/files/mountpoint');
1334
1335
		$this->assertTrue(
1336
			$view->lockFile('/mountpoint', ILockingProvider::LOCK_EXCLUSIVE, true),
1337
			'Can lock mount point'
1338
		);
1339
1340
		// no exception here because storage root was not locked
1341
		$mountedStorage->acquireLock('', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
1342
1343
		$thrown = false;
1344
		try {
1345
			$storage->acquireLock('/testuser/files/mountpoint', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
1346
		} catch (\OCP\Lock\LockedException $e) {
1347
			$thrown = true;
1348
		}
1349
		$this->assertTrue($thrown, 'Mount point path was locked on root storage');
1350
1351
		$lockingProvider->releaseAll();
1352
	}
1353
1354
	/**
1355
	 * Test that locks are on mount point paths and also mount root when requested
1356
	 */
1357 View Code Duplication
	public function testLockStorageRootButNotLocalMountPoint() {
1358
		$lockingProvider = \OC::$server->getLockingProvider();
1359
		$view = new \OC\Files\View('/testuser/files/');
1360
		$storage = new Temporary([]);
1361
		\OC\Files\Filesystem::mount($storage, [], '/');
1362
		$mountedStorage = new Temporary([]);
1363
		\OC\Files\Filesystem::mount($mountedStorage, [], '/testuser/files/mountpoint');
1364
1365
		$this->assertTrue(
1366
			$view->lockFile('/mountpoint', ILockingProvider::LOCK_EXCLUSIVE, false),
1367
			'Can lock mount point'
1368
		);
1369
1370
		$thrown = false;
1371
		try {
1372
			$mountedStorage->acquireLock('', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
1373
		} catch (\OCP\Lock\LockedException $e) {
1374
			$thrown = true;
1375
		}
1376
		$this->assertTrue($thrown, 'Mount point storage root was locked on original storage');
1377
1378
		// local mount point was not locked
1379
		$storage->acquireLock('/testuser/files/mountpoint', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
1380
1381
		$lockingProvider->releaseAll();
1382
	}
1383
1384
	/**
1385
	 * Test that locks are on mount point paths and also mount root when requested
1386
	 */
1387
	public function testLockMountPointPathFailReleasesBoth() {
1388
		$lockingProvider = \OC::$server->getLockingProvider();
1389
		$view = new \OC\Files\View('/testuser/files/');
1390
		$storage = new Temporary([]);
1391
		\OC\Files\Filesystem::mount($storage, [], '/');
1392
		$mountedStorage = new Temporary([]);
1393
		\OC\Files\Filesystem::mount($mountedStorage, [], '/testuser/files/mountpoint.txt');
1394
1395
		// this would happen if someone is writing on the mount point
1396
		$mountedStorage->acquireLock('', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
1397
1398
		$thrown = false;
1399
		try {
1400
			// this actually acquires two locks, one on the mount point and one on the storage root,
1401
			// but the one on the storage root will fail
1402
			$view->lockFile('/mountpoint.txt', ILockingProvider::LOCK_SHARED);
1403
		} catch (\OCP\Lock\LockedException $e) {
1404
			$thrown = true;
1405
		}
1406
		$this->assertTrue($thrown, 'Cannot acquire shared lock because storage root is already locked');
1407
1408
		// from here we expect that the lock on the local mount point was released properly
1409
		// so acquiring an exclusive lock will succeed
1410
		$storage->acquireLock('/testuser/files/mountpoint.txt', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
1411
1412
		$lockingProvider->releaseAll();
1413
	}
1414
1415 View Code Duplication
	public function dataLockPaths() {
1416
		return [
1417
			['/testuser/{folder}', ''],
1418
			['/testuser', '/{folder}'],
1419
			['', '/testuser/{folder}'],
1420
		];
1421
	}
1422
1423
	public function pathRelativeToFilesProvider() {
1424
		return [
1425
			['admin/files', ''],
1426
			['admin/files/x', 'x'],
1427
			['/admin/files', ''],
1428
			['/admin/files/sub', 'sub'],
1429
			['/admin/files/sub/', 'sub'],
1430
			['/admin/files/sub/sub2', 'sub/sub2'],
1431
			['//admin//files/sub//sub2', 'sub/sub2'],
1432
		];
1433
	}
1434
1435
	/**
1436
	 * @dataProvider pathRelativeToFilesProvider
1437
	 */
1438
	public function testGetPathRelativeToFiles($path, $expectedPath) {
1439
		$view = new \OC\Files\View();
1440
		$this->assertEquals($expectedPath, $view->getPathRelativeToFiles($path));
1441
	}
1442
1443
	public function pathRelativeToFilesProviderExceptionCases() {
1444
		return [
1445
			[''],
1446
			['x'],
1447
			['files'],
1448
			['/files'],
1449
			['/admin/files_versions/abc'],
1450
		];
1451
	}
1452
1453
	/**
1454
	 * @dataProvider pathRelativeToFilesProviderExceptionCases
1455
	 * @expectedException \InvalidArgumentException
1456
	 */
1457
	public function testGetPathRelativeToFilesWithInvalidArgument($path) {
1458
		$view = new \OC\Files\View();
1459
		$view->getPathRelativeToFiles($path);
1460
	}
1461
1462
	public function testChangeLock() {
1463
		$view = new \OC\Files\View('/testuser/files/');
1464
		$storage = new Temporary(array());
1465
		\OC\Files\Filesystem::mount($storage, [], '/');
1466
1467
		$view->lockFile('/test/sub', ILockingProvider::LOCK_SHARED);
1468
		$this->assertTrue($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_SHARED));
1469
		$this->assertFalse($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_EXCLUSIVE));
1470
1471
		$view->changeLock('//test/sub', ILockingProvider::LOCK_EXCLUSIVE);
1472
		$this->assertTrue($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_EXCLUSIVE));
1473
1474
		$view->changeLock('test/sub', ILockingProvider::LOCK_SHARED);
1475
		$this->assertTrue($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_SHARED));
1476
1477
		$view->unlockFile('/test/sub/', ILockingProvider::LOCK_SHARED);
1478
1479
		$this->assertFalse($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_SHARED));
1480
		$this->assertFalse($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_EXCLUSIVE));
1481
1482
	}
1483
1484
	public function hookPathProvider() {
1485
		return [
1486
			['/foo/files', '/foo', true],
1487
			['/foo/files/bar', '/foo', true],
1488
			['/foo', '/foo', false],
1489
			['/foo', '/files/foo', true],
1490
			['/foo', 'filesfoo', false],
1491
			['', '/foo/files', true],
1492
			['', '/foo/files/bar.txt', true]
1493
		];
1494
	}
1495
1496
	/**
1497
	 * @dataProvider hookPathProvider
1498
	 * @param $root
1499
	 * @param $path
1500
	 * @param $shouldEmit
1501
	 */
1502
	public function testHookPaths($root, $path, $shouldEmit) {
1503
		$filesystemReflection = new \ReflectionClass('\OC\Files\Filesystem');
1504
		$defaultRootValue = $filesystemReflection->getProperty('defaultInstance');
1505
		$defaultRootValue->setAccessible(true);
1506
		$oldRoot = $defaultRootValue->getValue();
1507
		$defaultView = new \OC\Files\View('/foo/files');
1508
		$defaultRootValue->setValue($defaultView);
1509
		$view = new \OC\Files\View($root);
1510
		$result = \Test_Helper::invokePrivate($view, 'shouldEmitHooks', [$path]);
1511
		$defaultRootValue->setValue($oldRoot);
1512
		$this->assertEquals($shouldEmit, $result);
1513
	}
1514
1515
	/**
1516
	 * Create test movable mount points
1517
	 *
1518
	 * @param array $mountPoints array of mount point locations
1519
	 * @return array array of MountPoint objects
1520
	 */
1521
	private function createTestMovableMountPoints($mountPoints) {
1522
		$mounts = [];
1523
		foreach ($mountPoints as $mountPoint) {
1524
			$storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
1525
				->setMethods([])
1526
				->getMock();
1527
1528
			$mounts[] = $this->getMock(
1529
				'\Test\TestMoveableMountPoint',
1530
				['moveMount'],
1531
				[$storage, $mountPoint]
1532
			);
1533
		}
1534
1535
		$mountProvider = $this->getMock('\OCP\Files\Config\IMountProvider');
1536
		$mountProvider->expects($this->any())
1537
			->method('getMountsForUser')
1538
			->will($this->returnValue($mounts));
1539
1540
		$mountProviderCollection = \OC::$server->getMountProviderCollection();
1541
		$mountProviderCollection->registerProvider($mountProvider);
1542
1543
		return $mounts;
1544
	}
1545
1546
	/**
1547
	 * Test mount point move
1548
	 */
1549
	public function testMountPointMove() {
1550
		$this->loginAsUser($this->user);
1551
1552
		list($mount1, $mount2) = $this->createTestMovableMountPoints([
1553
			$this->user . '/files/mount1',
1554
			$this->user . '/files/mount2',
1555
		]);
1556
		$mount1->expects($this->once())
1557
			->method('moveMount')
1558
			->will($this->returnValue(true));
1559
1560
		$mount2->expects($this->once())
1561
			->method('moveMount')
1562
			->will($this->returnValue(true));
1563
1564
		$view = new \OC\Files\View('/' . $this->user . '/files/');
1565
		$view->mkdir('sub');
1566
1567
		$this->assertTrue($view->rename('mount1', 'renamed_mount'), 'Can rename mount point');
1568
		$this->assertTrue($view->rename('mount2', 'sub/moved_mount'), 'Can move a mount point into a subdirectory');
1569
	}
1570
	/**
1571
	 * Test that moving a mount point into another is forbidden
1572
	 */
1573
	public function testMoveMountPointIntoAnother() {
1574
		$this->loginAsUser($this->user);
1575
1576
		list($mount1, $mount2) = $this->createTestMovableMountPoints([
1577
			$this->user . '/files/mount1',
1578
			$this->user . '/files/mount2',
1579
		]);
1580
1581
		$mount1->expects($this->never())
1582
			->method('moveMount');
1583
1584
		$mount2->expects($this->never())
1585
			->method('moveMount');
1586
1587
		$view = new \OC\Files\View('/' . $this->user . '/files/');
1588
1589
		$this->assertFalse($view->rename('mount1', 'mount2'), 'Cannot overwrite another mount point');
1590
		$this->assertFalse($view->rename('mount1', 'mount2/sub'), 'Cannot move a mount point into another');
1591
	}
1592
	/**
1593
	 * Test that moving a mount point into a shared folder is forbidden
1594
	 */
1595
	public function testMoveMountPointIntoSharedFolder() {
1596
		$this->loginAsUser($this->user);
1597
1598
		list($mount1) = $this->createTestMovableMountPoints([
1599
			$this->user . '/files/mount1',
1600
		]);
1601
1602
		$mount1->expects($this->never())
1603
			->method('moveMount');
1604
1605
		$view = new \OC\Files\View('/' . $this->user . '/files/');
1606
		$view->mkdir('shareddir');
1607
		$view->mkdir('shareddir/sub');
1608
		$view->mkdir('shareddir/sub2');
1609
1610
		$fileId = $view->getFileInfo('shareddir')->getId();
1611
		$userObject = \OC::$server->getUserManager()->createUser('test2', 'IHateNonMockableStaticClasses');
1612
		$this->assertTrue(\OCP\Share::shareItem('folder', $fileId, \OCP\Share::SHARE_TYPE_USER, 'test2', \OCP\Constants::PERMISSION_READ));
1613
1614
		$this->assertFalse($view->rename('mount1', 'shareddir'), 'Cannot overwrite shared folder');
1615
		$this->assertFalse($view->rename('mount1', 'shareddir/sub'), 'Cannot move mount point into shared folder');
1616
		$this->assertFalse($view->rename('mount1', 'shareddir/sub/sub2'), 'Cannot move mount point into shared subfolder');
1617
1618
		$this->assertTrue(\OCP\Share::unshare('folder', $fileId, \OCP\Share::SHARE_TYPE_USER, 'test2'));
1619
		$userObject->delete();
1620
	}
1621
1622
	public function basicOperationProviderForLocks() {
1623
		return [
1624
			// --- write hook ----
1625
			[
1626
				'touch',
1627
				['touch-create.txt'],
1628
				'touch-create.txt',
1629
				'create',
1630
				ILockingProvider::LOCK_SHARED,
1631
				ILockingProvider::LOCK_EXCLUSIVE,
1632
				ILockingProvider::LOCK_SHARED,
1633
			],
1634
			[
1635
				'fopen',
1636
				['test-write.txt', 'w'],
1637
				'test-write.txt',
1638
				'write',
1639
				ILockingProvider::LOCK_SHARED,
1640
				ILockingProvider::LOCK_EXCLUSIVE,
1641
				null,
1642
				// exclusive lock stays until fclose
1643
				ILockingProvider::LOCK_EXCLUSIVE,
1644
			],
1645
			[
1646
				'mkdir',
1647
				['newdir'],
1648
				'newdir',
1649
				'write',
1650
				ILockingProvider::LOCK_SHARED,
1651
				ILockingProvider::LOCK_EXCLUSIVE,
1652
				ILockingProvider::LOCK_SHARED,
1653
			],
1654
			[
1655
				'file_put_contents',
1656
				['file_put_contents.txt', 'blah'],
1657
				'file_put_contents.txt',
1658
				'write',
1659
				ILockingProvider::LOCK_SHARED,
1660
				ILockingProvider::LOCK_EXCLUSIVE,
1661
				ILockingProvider::LOCK_SHARED,
1662
			],
1663
1664
			// ---- delete hook ----
1665
			[
1666
				'rmdir',
1667
				['dir'],
1668
				'dir',
1669
				'delete',
1670
				ILockingProvider::LOCK_SHARED,
1671
				ILockingProvider::LOCK_EXCLUSIVE,
1672
				ILockingProvider::LOCK_SHARED,
1673
			],
1674
			[
1675
				'unlink',
1676
				['test.txt'],
1677
				'test.txt',
1678
				'delete',
1679
				ILockingProvider::LOCK_SHARED,
1680
				ILockingProvider::LOCK_EXCLUSIVE,
1681
				ILockingProvider::LOCK_SHARED,
1682
			],
1683
1684
			// ---- read hook (no post hooks) ----
1685
			[
1686
				'file_get_contents',
1687
				['test.txt'],
1688
				'test.txt',
1689
				'read',
1690
				ILockingProvider::LOCK_SHARED,
1691
				ILockingProvider::LOCK_SHARED,
1692
				null,
1693
			],
1694
			[
1695
				'fopen',
1696
				['test.txt', 'r'],
1697
				'test.txt',
1698
				'read',
1699
				ILockingProvider::LOCK_SHARED,
1700
				ILockingProvider::LOCK_SHARED,
1701
				null,
1702
			],
1703
			[
1704
				'opendir',
1705
				['dir'],
1706
				'dir',
1707
				'read',
1708
				ILockingProvider::LOCK_SHARED,
1709
				ILockingProvider::LOCK_SHARED,
1710
				null,
1711
			],
1712
1713
			// ---- no lock, touch hook ---
1714
			['touch', ['test.txt'], 'test.txt', 'touch', null, null, null],
1715
1716
			// ---- no hooks, no locks ---
1717
			['is_dir', ['dir'], 'dir', null],
1718
			['is_file', ['dir'], 'dir', null],
1719
			['stat', ['dir'], 'dir', null],
1720
			['filetype', ['dir'], 'dir', null],
1721
			['filesize', ['dir'], 'dir', null],
1722
			['isCreatable', ['dir'], 'dir', null],
1723
			['isReadable', ['dir'], 'dir', null],
1724
			['isUpdatable', ['dir'], 'dir', null],
1725
			['isDeletable', ['dir'], 'dir', null],
1726
			['isSharable', ['dir'], 'dir', null],
1727
			['file_exists', ['dir'], 'dir', null],
1728
			['filemtime', ['dir'], 'dir', null],
1729
		];
1730
	}
1731
1732
	/**
1733
	 * Test whether locks are set before and after the operation
1734
	 *
1735
	 * @dataProvider basicOperationProviderForLocks
1736
	 *
1737
	 * @param string $operation operation name on the view
1738
	 * @param array $operationArgs arguments for the operation
1739
	 * @param string $lockedPath path of the locked item to check
1740
	 * @param string $hookType hook type
1741
	 * @param int $expectedLockBefore expected lock during pre hooks
1742
	 * @param int $expectedLockduring expected lock during operation
0 ignored issues
show
Documentation introduced by
There is no parameter named $expectedLockduring. Did you maybe mean $expectedLockDuring?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
1743
	 * @param int $expectedLockAfter expected lock during post hooks
1744
	 * @param int $expectedStrayLock expected lock after returning, should
1745
	 * be null (unlock) for most operations
1746
	 */
1747
	public function testLockBasicOperation(
1748
		$operation,
1749
		$operationArgs,
1750
		$lockedPath,
1751
		$hookType,
1752
		$expectedLockBefore = ILockingProvider::LOCK_SHARED,
1753
		$expectedLockDuring = ILockingProvider::LOCK_SHARED,
1754
		$expectedLockAfter = ILockingProvider::LOCK_SHARED,
1755
		$expectedStrayLock = null
1756
	) {
1757
		$view = new \OC\Files\View('/' . $this->user . '/files/');
1758
1759
		$storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
1760
			->setMethods([$operation])
1761
			->getMock();
1762
1763
		\OC\Files\Filesystem::mount($storage, array(), $this->user . '/');
1764
1765
		// work directly on disk because mkdir might be mocked
1766
		$realPath = $storage->getSourcePath('');
1767
		mkdir($realPath . '/files');
1768
		mkdir($realPath . '/files/dir');
1769
		file_put_contents($realPath . '/files/test.txt', 'blah');
1770
		$storage->getScanner()->scan('files');
1771
1772
		$storage->expects($this->once())
1773
			->method($operation)
1774
			->will($this->returnCallback(
1775
				function() use ($view, $lockedPath, &$lockTypeDuring){
1776
					$lockTypeDuring = $this->getFileLockType($view, $lockedPath);
1777
1778
					return true;
1779
				}
1780
			));
1781
1782
		$this->assertNull($this->getFileLockType($view, $lockedPath), 'File not locked before operation');
1783
1784
		$this->connectMockHooks($hookType, $view, $lockedPath, $lockTypePre, $lockTypePost);
1785
1786
		// do operation
1787
		call_user_func_array(array($view, $operation), $operationArgs);
1788
1789
		if ($hookType !== null) {
1790
			$this->assertEquals($expectedLockBefore, $lockTypePre, 'File locked properly during pre-hook');
1791
			$this->assertEquals($expectedLockAfter, $lockTypePost, 'File locked properly during post-hook');
1792
			$this->assertEquals($expectedLockDuring, $lockTypeDuring, 'File locked properly during operation');
1793
		} else {
1794
			$this->assertNull($lockTypeDuring, 'File not locked during operation');
1795
		}
1796
1797
		$this->assertEquals($expectedStrayLock, $this->getFileLockType($view, $lockedPath));
1798
	}
1799
1800
	/**
1801
	 * Test locks for file_put_content with stream.
1802
	 * This code path uses $storage->fopen instead
1803
	 */
1804
	public function testLockFilePutContentWithStream() {
1805
		$view = new \OC\Files\View('/' . $this->user . '/files/');
1806
1807
		$path = 'test_file_put_contents.txt';
1808
		$storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
1809
			->setMethods(['fopen'])
1810
			->getMock();
1811
1812
		\OC\Files\Filesystem::mount($storage, array(), $this->user . '/');
1813
		$storage->mkdir('files');
1814
1815
		$storage->expects($this->once())
1816
			->method('fopen')
1817
			->will($this->returnCallback(
1818
				function() use ($view, $path, &$lockTypeDuring){
1819
					$lockTypeDuring = $this->getFileLockType($view, $path);
1820
1821
					return fopen('php://temp', 'r+');
1822
				}
1823
			));
1824
1825
		$this->connectMockHooks('write', $view, $path, $lockTypePre, $lockTypePost);
1826
1827
		$this->assertNull($this->getFileLockType($view, $path), 'File not locked before operation');
1828
1829
		// do operation
1830
		$view->file_put_contents($path, fopen('php://temp', 'r+'));
1831
1832
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypePre, 'File locked properly during pre-hook');
1833
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypePost, 'File locked properly during post-hook');
1834
		$this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeDuring, 'File locked properly during operation');
1835
1836
		$this->assertNull($this->getFileLockType($view, $path));
1837
	}
1838
1839
	/**
1840
	 * Test locks for fopen with fclose at the end
1841
	 */
1842
	public function testLockFopen() {
1843
		$view = new \OC\Files\View('/' . $this->user . '/files/');
1844
1845
		$path = 'test_file_put_contents.txt';
1846
		$storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
1847
			->setMethods(['fopen'])
1848
			->getMock();
1849
1850
		\OC\Files\Filesystem::mount($storage, array(), $this->user . '/');
1851
		$storage->mkdir('files');
1852
1853
		$storage->expects($this->once())
1854
			->method('fopen')
1855
			->will($this->returnCallback(
1856
				function() use ($view, $path, &$lockTypeDuring){
1857
					$lockTypeDuring = $this->getFileLockType($view, $path);
1858
1859
					return fopen('php://temp', 'r+');
1860
				}
1861
			));
1862
1863
		$this->connectMockHooks('write', $view, $path, $lockTypePre, $lockTypePost);
1864
1865
		$this->assertNull($this->getFileLockType($view, $path), 'File not locked before operation');
1866
1867
		// do operation
1868
		$res = $view->fopen($path, 'w');
1869
1870
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypePre, 'File locked properly during pre-hook');
1871
		$this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeDuring, 'File locked properly during operation');
1872
		$this->assertNull(null, $lockTypePost, 'No post hook, no lock check possible');
1873
1874
		$this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeDuring, 'File still locked after fopen');
1875
1876
		fclose($res);
1877
1878
		$this->assertNull($this->getFileLockType($view, $path), 'File unlocked after fclose');
1879
	}
1880
1881
	/**
1882
	 * Test locks for fopen with fclose at the end
1883
	 *
1884
	 * @dataProvider basicOperationProviderForLocks
1885
	 *
1886
	 * @param string $operation operation name on the view
1887
	 * @param array $operationArgs arguments for the operation
1888
	 * @param string $path path of the locked item to check
1889
	 */
1890
	public function testLockBasicOperationUnlocksAfterException(
1891
		$operation,
1892
		$operationArgs,
1893
		$path
1894
	) {
1895
		$view = new \OC\Files\View('/' . $this->user . '/files/');
1896
1897
		$storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
1898
			->setMethods([$operation])
1899
			->getMock();
1900
1901
		\OC\Files\Filesystem::mount($storage, array(), $this->user . '/');
1902
1903
		// work directly on disk because mkdir might be mocked
1904
		$realPath = $storage->getSourcePath('');
1905
		mkdir($realPath . '/files');
1906
		mkdir($realPath . '/files/dir');
1907
		file_put_contents($realPath . '/files/test.txt', 'blah');
1908
		$storage->getScanner()->scan('files');
1909
1910
		$storage->expects($this->once())
1911
			->method($operation)
1912
			->will($this->returnCallback(
1913
				function() {
1914
					throw new \Exception('Simulated exception');
1915
				}
1916
			));
1917
1918
		$thrown = false;
1919
		try {
1920
			call_user_func_array(array($view, $operation), $operationArgs);
1921
		} catch (\Exception $e) {
1922
			$thrown = true;
1923
			$this->assertEquals('Simulated exception', $e->getMessage());
1924
		}
1925
		$this->assertTrue($thrown, 'Exception was rethrown');
1926
		$this->assertNull($this->getFileLockType($view, $path), 'File got unlocked after exception');
1927
	}
1928
1929
	/**
1930
	 * Test locks for fopen with fclose at the end
1931
	 *
1932
	 * @dataProvider basicOperationProviderForLocks
1933
	 *
1934
	 * @param string $operation operation name on the view
1935
	 * @param array $operationArgs arguments for the operation
1936
	 * @param string $path path of the locked item to check
1937
	 * @param string $hookType hook type
1938
	 */
1939
	public function testLockBasicOperationUnlocksAfterCancelledHook(
1940
		$operation,
1941
		$operationArgs,
1942
		$path,
1943
		$hookType
1944
	) {
1945
		$view = new \OC\Files\View('/' . $this->user . '/files/');
1946
1947
		$storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
1948
			->setMethods([$operation])
1949
			->getMock();
1950
1951
		\OC\Files\Filesystem::mount($storage, array(), $this->user . '/');
1952
		$storage->mkdir('files');
1953
1954
		\OCP\Util::connectHook(
1955
			\OC\Files\Filesystem::CLASSNAME,
1956
			$hookType,
1957
			'\Test\HookHelper',
1958
			'cancellingCallback'
1959
		);
1960
1961
		call_user_func_array(array($view, $operation), $operationArgs);
1962
1963
		$this->assertNull($this->getFileLockType($view, $path), 'File got unlocked after exception');
1964
	}
1965
1966
	public function lockFileRenameOrCopyDataProvider() {
1967
		return [
1968
			['rename', ILockingProvider::LOCK_EXCLUSIVE],
1969
			['copy', ILockingProvider::LOCK_SHARED],
1970
		];
1971
	}
1972
1973
	/**
1974
	 * Test locks for rename or copy operation
1975
	 *
1976
	 * @dataProvider lockFileRenameOrCopyDataProvider
1977
	 *
1978
	 * @param string $operation operation to be done on the view
1979
	 * @param int $expectedLockTypeSourceDuring expected lock type on source file during
1980
	 * the operation
1981
	 */
1982
	public function testLockFileRename($operation, $expectedLockTypeSourceDuring) {
1983
		$view = new \OC\Files\View('/' . $this->user . '/files/');
1984
1985
		$storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
1986
			->setMethods([$operation])
1987
			->getMock();
1988
1989
		$sourcePath = 'original.txt';
1990
		$targetPath = 'target.txt';
1991
1992
		\OC\Files\Filesystem::mount($storage, array(), $this->user . '/');
1993
		$storage->mkdir('files');
1994
		$view->file_put_contents($sourcePath, 'meh');
1995
1996
		$storage->expects($this->once())
1997
			->method($operation)
1998
			->will($this->returnCallback(
1999 View Code Duplication
				function() use ($view, $sourcePath, $targetPath, &$lockTypeSourceDuring, &$lockTypeTargetDuring){
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2000
					$lockTypeSourceDuring = $this->getFileLockType($view, $sourcePath);
2001
					$lockTypeTargetDuring = $this->getFileLockType($view, $targetPath);
2002
2003
					return true;
2004
				}
2005
			));
2006
2007
		$this->connectMockHooks($operation, $view, $sourcePath, $lockTypeSourcePre, $lockTypeSourcePost);
2008
		$this->connectMockHooks($operation, $view, $targetPath, $lockTypeTargetPre, $lockTypeTargetPost);
2009
2010
		$this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked before operation');
2011
		$this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked before operation');
2012
2013
		$view->$operation($sourcePath, $targetPath);
2014
2015
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePre, 'Source file locked properly during pre-hook');
2016
		$this->assertEquals($expectedLockTypeSourceDuring, $lockTypeSourceDuring, 'Source file locked properly during operation');
2017
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePost, 'Source file locked properly during post-hook');
2018
2019
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPre, 'Target file locked properly during pre-hook');
2020
		$this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeTargetDuring, 'Target file locked properly during operation');
2021
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPost, 'Target file locked properly during post-hook');
2022
2023
		$this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked after operation');
2024
		$this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked after operation');
2025
	}
2026
2027
	/**
2028
	 * simulate a failed copy operation.
2029
	 * We expect that we catch the exception, free the lock and re-throw it.
2030
	 *
2031
	 * @expectedException \Exception
2032
	 */
2033
	public function testLockFileCopyException() {
2034
		$view = new \OC\Files\View('/' . $this->user . '/files/');
2035
2036
		$storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
2037
			->setMethods(['copy'])
2038
			->getMock();
2039
2040
		$sourcePath = 'original.txt';
2041
		$targetPath = 'target.txt';
2042
2043
		\OC\Files\Filesystem::mount($storage, array(), $this->user . '/');
2044
		$storage->mkdir('files');
2045
		$view->file_put_contents($sourcePath, 'meh');
2046
2047
		$storage->expects($this->once())
2048
			->method('copy')
2049
			->will($this->returnCallback(
2050
				function() {
2051
					throw new \Exception();
2052
				}
2053
			));
2054
2055
		$this->connectMockHooks('copy', $view, $sourcePath, $lockTypeSourcePre, $lockTypeSourcePost);
2056
		$this->connectMockHooks('copy', $view, $targetPath, $lockTypeTargetPre, $lockTypeTargetPost);
2057
2058
		$this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked before operation');
2059
		$this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked before operation');
2060
2061
		try {
2062
			$view->copy($sourcePath, $targetPath);
2063
		} catch (\Exception $e) {
2064
			$this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked after operation');
2065
			$this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked after operation');
2066
			throw $e;
2067
		}
2068
	}
2069
2070
	/**
2071
	 * Test rename operation: unlock first path when second path was locked
2072
	 */
2073
	public function testLockFileRenameUnlockOnException() {
2074
		$this->loginAsUser('test');
2075
2076
		$view = new \OC\Files\View('/' . $this->user . '/files/');
2077
2078
		$sourcePath = 'original.txt';
2079
		$targetPath = 'target.txt';
2080
		$view->file_put_contents($sourcePath, 'meh');
2081
2082
		// simulate that the target path is already locked
2083
		$view->lockFile($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
2084
2085
		$this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked before operation');
2086
		$this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $this->getFileLockType($view, $targetPath), 'Target file is locked before operation');
2087
2088
		$thrown = false;
2089
		try {
2090
			$view->rename($sourcePath, $targetPath);
2091
		} catch (\OCP\Lock\LockedException $e) {
2092
			$thrown = true;
2093
		}
2094
2095
		$this->assertTrue($thrown, 'LockedException thrown');
2096
2097
		$this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked after operation');
2098
		$this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $this->getFileLockType($view, $targetPath), 'Target file still locked after operation');
2099
2100
		$view->unlockFile($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
2101
	}
2102
2103
	public function lockFileRenameOrCopyCrossStorageDataProvider() {
2104
		return [
2105
			['rename', 'moveFromStorage', ILockingProvider::LOCK_EXCLUSIVE],
2106
			['copy', 'copyFromStorage', ILockingProvider::LOCK_SHARED],
2107
		];
2108
	}
2109
2110
	/**
2111
	 * Test locks for rename or copy operation cross-storage
2112
	 *
2113
	 * @dataProvider lockFileRenameOrCopyCrossStorageDataProvider
2114
	 *
2115
	 * @param string $viewOperation operation to be done on the view
2116
	 * @param string $storageOperation operation to be mocked on the storage
2117
	 * @param int $expectedLockTypeSourceDuring expected lock type on source file during
2118
	 * the operation
2119
	 */
2120
	public function testLockFileRenameCrossStorage($viewOperation, $storageOperation, $expectedLockTypeSourceDuring) {
2121
		$view = new \OC\Files\View('/' . $this->user . '/files/');
2122
2123
		$storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
2124
			->setMethods([$storageOperation])
2125
			->getMock();
2126
		$storage2 = $this->getMockBuilder('\OC\Files\Storage\Temporary')
2127
			->setMethods([$storageOperation])
2128
			->getMock();
2129
2130
		$sourcePath = 'original.txt';
2131
		$targetPath = 'substorage/target.txt';
2132
2133
		\OC\Files\Filesystem::mount($storage, array(), $this->user . '/');
2134
		\OC\Files\Filesystem::mount($storage2, array(), $this->user . '/files/substorage');
2135
		$storage->mkdir('files');
2136
		$view->file_put_contents($sourcePath, 'meh');
2137
2138
		$storage->expects($this->never())
2139
			->method($storageOperation);
2140
		$storage2->expects($this->once())
2141
			->method($storageOperation)
2142
			->will($this->returnCallback(
2143 View Code Duplication
				function() use ($view, $sourcePath, $targetPath, &$lockTypeSourceDuring, &$lockTypeTargetDuring){
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2144
					$lockTypeSourceDuring = $this->getFileLockType($view, $sourcePath);
2145
					$lockTypeTargetDuring = $this->getFileLockType($view, $targetPath);
2146
2147
					return true;
2148
				}
2149
			));
2150
2151
		$this->connectMockHooks($viewOperation, $view, $sourcePath, $lockTypeSourcePre, $lockTypeSourcePost);
2152
		$this->connectMockHooks($viewOperation, $view, $targetPath, $lockTypeTargetPre, $lockTypeTargetPost);
2153
2154
		$this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked before operation');
2155
		$this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked before operation');
2156
2157
		$view->$viewOperation($sourcePath, $targetPath);
2158
2159
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePre, 'Source file locked properly during pre-hook');
2160
		$this->assertEquals($expectedLockTypeSourceDuring, $lockTypeSourceDuring, 'Source file locked properly during operation');
2161
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePost, 'Source file locked properly during post-hook');
2162
2163
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPre, 'Target file locked properly during pre-hook');
2164
		$this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeTargetDuring, 'Target file locked properly during operation');
2165
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPost, 'Target file locked properly during post-hook');
2166
2167
		$this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked after operation');
2168
		$this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked after operation');
2169
	}
2170
2171
	/**
2172
	 * Test locks when moving a mount point
2173
	 */
2174
	public function testLockMoveMountPoint() {
2175
		$this->loginAsUser('test');
2176
2177
		list($mount) = $this->createTestMovableMountPoints([
2178
			$this->user . '/files/substorage',
2179
		]);
2180
2181
		$view = new \OC\Files\View('/' . $this->user . '/files/');
2182
		$view->mkdir('subdir');
2183
2184
		$sourcePath = 'substorage';
2185
		$targetPath = 'subdir/substorage_moved';
2186
2187
		$mount->expects($this->once())
2188
			->method('moveMount')
2189
			->will($this->returnCallback(
2190
				function($target) use ($mount, $view, $sourcePath, $targetPath, &$lockTypeSourceDuring, &$lockTypeTargetDuring, &$lockTypeSharedRootDuring){
2191
					$lockTypeSourceDuring = $this->getFileLockType($view, $sourcePath, true);
2192
					$lockTypeTargetDuring = $this->getFileLockType($view, $targetPath, true);
2193
2194
					$lockTypeSharedRootDuring = $this->getFileLockType($view, $sourcePath, false);
2195
2196
					$mount->setMountPoint($target);
2197
2198
					return true;
2199
				}
2200
			));
2201
2202
		$this->connectMockHooks('rename', $view, $sourcePath, $lockTypeSourcePre, $lockTypeSourcePost, true);
2203
		$this->connectMockHooks('rename', $view, $targetPath, $lockTypeTargetPre, $lockTypeTargetPost, true);
2204
		// in pre-hook, mount point is still on $sourcePath
2205
		$this->connectMockHooks('rename', $view, $sourcePath, $lockTypeSharedRootPre, $dummy, false);
2206
		// in post-hook, mount point is now on $targetPath
2207
		$this->connectMockHooks('rename', $view, $targetPath, $dummy, $lockTypeSharedRootPost, false);
2208
2209
		$this->assertNull($this->getFileLockType($view, $sourcePath, false), 'Shared storage root not locked before operation');
2210
		$this->assertNull($this->getFileLockType($view, $sourcePath, true), 'Source path not locked before operation');
2211
		$this->assertNull($this->getFileLockType($view, $targetPath, true), 'Target path not locked before operation');
2212
2213
		$view->rename($sourcePath, $targetPath);
2214
2215
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePre, 'Source path locked properly during pre-hook');
2216
		$this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeSourceDuring, 'Source path locked properly during operation');
2217
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePost, 'Source path locked properly during post-hook');
2218
2219
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPre, 'Target path locked properly during pre-hook');
2220
		$this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeTargetDuring, 'Target path locked properly during operation');
2221
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPost, 'Target path locked properly during post-hook');
2222
2223
		$this->assertNull($lockTypeSharedRootPre, 'Shared storage root not locked during pre-hook');
2224
		$this->assertNull($lockTypeSharedRootDuring, 'Shared storage root not locked during move');
2225
		$this->assertNull($lockTypeSharedRootPost, 'Shared storage root not locked during post-hook');
2226
2227
		$this->assertNull($this->getFileLockType($view, $sourcePath, false), 'Shared storage root not locked after operation');
2228
		$this->assertNull($this->getFileLockType($view, $sourcePath, true), 'Source path not locked after operation');
2229
		$this->assertNull($this->getFileLockType($view, $targetPath, true), 'Target path not locked after operation');
2230
	}
2231
2232
	/**
2233
	 * Connect hook callbacks for hook type
2234
	 *
2235
	 * @param string $hookType hook type or null for none
2236
	 * @param \OC\Files\View $view view to check the lock on
2237
	 * @param string $path path for which to check the lock
2238
	 * @param int $lockTypePre variable to receive lock type that was active in the pre-hook
2239
	 * @param int $lockTypePost variable to receive lock type that was active in the post-hook
2240
	 * @param bool $onMountPoint true to check the mount point instead of the
2241
	 * mounted storage
2242
	 */
2243
	private function connectMockHooks($hookType, $view, $path, &$lockTypePre, &$lockTypePost, $onMountPoint = false) {
2244
		if ($hookType === null) {
2245
			return;
2246
		}
2247
2248
		$eventHandler = $this->getMockBuilder('\stdclass')
2249
			->setMethods(['preCallback', 'postCallback'])
2250
			->getMock();
2251
2252
		$eventHandler->expects($this->any())
2253
			->method('preCallback')
2254
			->will($this->returnCallback(
2255
				function() use ($view, $path, $onMountPoint, &$lockTypePre){
2256
					$lockTypePre = $this->getFileLockType($view, $path, $onMountPoint);
2257
				}
2258
			));
2259
		$eventHandler->expects($this->any())
2260
			->method('postCallback')
2261
			->will($this->returnCallback(
2262
				function() use ($view, $path, $onMountPoint, &$lockTypePost){
2263
					$lockTypePost = $this->getFileLockType($view, $path, $onMountPoint);
2264
				}
2265
			));
2266
2267
		if ($hookType !== null) {
2268
			\OCP\Util::connectHook(
2269
				\OC\Files\Filesystem::CLASSNAME,
2270
				$hookType,
2271
				$eventHandler,
2272
				'preCallback'
2273
			);
2274
			\OCP\Util::connectHook(
2275
				\OC\Files\Filesystem::CLASSNAME,
2276
				'post_' . $hookType,
2277
				$eventHandler,
2278
				'postCallback'
2279
			);
2280
		}
2281
	}
2282
2283
	/**
2284
	 * Returns the file lock type
2285
	 *
2286
	 * @param \OC\Files\View $view view
2287
	 * @param string $path path
2288
	 * @param bool $onMountPoint true to check the mount point instead of the
2289
	 * mounted storage
2290
	 *
2291
	 * @return int lock type or null if file was not locked
2292
	 */
2293
	private function getFileLockType(\OC\Files\View $view, $path, $onMountPoint = false) {
2294
		if ($this->isFileLocked($view, $path, ILockingProvider::LOCK_EXCLUSIVE, $onMountPoint)) {
2295
			return ILockingProvider::LOCK_EXCLUSIVE;
2296
		} else if ($this->isFileLocked($view, $path, ILockingProvider::LOCK_SHARED, $onMountPoint)) {
2297
			return ILockingProvider::LOCK_SHARED;
2298
		}
2299
		return null;
2300
	}
2301
}
2302