Completed
Pull Request — stable8.2 (#24656)
by Joas
12:22
created

View::absolutePathProvider()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 11
rs 9.4285
cc 1
eloc 9
nc 1
nop 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
	/**
485
	 * @medium
486
	 */
487 View Code Duplication
	public function testUnlinkRootMustFail() {
488
		$storage1 = $this->getTestStorage();
489
		$storage2 = $this->getTestStorage();
490
		\OC\Files\Filesystem::mount($storage1, array(), '/');
491
		\OC\Files\Filesystem::mount($storage2, array(), '/substorage');
492
493
		$rootView = new \OC\Files\View('');
494
		$rootView->file_put_contents('/foo.txt', 'asd');
495
		$rootView->file_put_contents('/substorage/bar.txt', 'asd');
496
497
		$this->assertFalse($rootView->unlink(''));
498
		$this->assertFalse($rootView->unlink('/'));
499
		$this->assertFalse($rootView->unlink('substorage'));
500
		$this->assertFalse($rootView->unlink('/substorage'));
501
	}
502
503
	/**
504
	 * @medium
505
	 */
506
	public function testTouch() {
507
		$storage = $this->getTestStorage(true, '\Test\Files\TemporaryNoTouch');
508
509
		\OC\Files\Filesystem::mount($storage, array(), '/');
510
511
		$rootView = new \OC\Files\View('');
512
		$oldCachedData = $rootView->getFileInfo('foo.txt');
513
514
		$rootView->touch('foo.txt', 500);
515
516
		$cachedData = $rootView->getFileInfo('foo.txt');
517
		$this->assertEquals(500, $cachedData['mtime']);
518
		$this->assertEquals($oldCachedData['storage_mtime'], $cachedData['storage_mtime']);
519
520
		$rootView->putFileInfo('foo.txt', array('storage_mtime' => 1000)); //make sure the watcher detects the change
521
		$rootView->file_put_contents('foo.txt', 'asd');
522
		$cachedData = $rootView->getFileInfo('foo.txt');
523
		$this->assertGreaterThanOrEqual($oldCachedData['mtime'], $cachedData['mtime']);
524
		$this->assertEquals($cachedData['storage_mtime'], $cachedData['mtime']);
525
	}
526
527
	/**
528
	 * @medium
529
	 */
530
	public function testViewHooks() {
531
		$storage1 = $this->getTestStorage();
532
		$storage2 = $this->getTestStorage();
533
		$defaultRoot = \OC\Files\Filesystem::getRoot();
534
		\OC\Files\Filesystem::mount($storage1, array(), '/');
535
		\OC\Files\Filesystem::mount($storage2, array(), $defaultRoot . '/substorage');
536
		\OC_Hook::connect('OC_Filesystem', 'post_write', $this, 'dummyHook');
537
538
		$rootView = new \OC\Files\View('');
539
		$subView = new \OC\Files\View($defaultRoot . '/substorage');
540
		$this->hookPath = null;
541
542
		$rootView->file_put_contents('/foo.txt', 'asd');
543
		$this->assertNull($this->hookPath);
544
545
		$subView->file_put_contents('/foo.txt', 'asd');
546
		$this->assertEquals('/substorage/foo.txt', $this->hookPath);
547
	}
548
549
	private $hookPath;
550
551
	public function dummyHook($params) {
552
		$this->hookPath = $params['path'];
553
	}
554
555
	public function testSearchNotOutsideView() {
556
		$storage1 = $this->getTestStorage();
557
		\OC\Files\Filesystem::mount($storage1, array(), '/');
558
		$storage1->rename('folder', 'foo');
559
		$scanner = $storage1->getScanner();
560
		$scanner->scan('');
561
562
		$view = new \OC\Files\View('/foo');
563
564
		$result = $view->search('.txt');
565
		$this->assertCount(1, $result);
566
	}
567
568
	/**
569
	 * @param bool $scan
570
	 * @param string $class
571
	 * @return \OC\Files\Storage\Storage
572
	 */
573 View Code Duplication
	private function getTestStorage($scan = true, $class = '\OC\Files\Storage\Temporary') {
574
		/**
575
		 * @var \OC\Files\Storage\Storage $storage
576
		 */
577
		$storage = new $class(array());
578
		$textData = "dummy file data\n";
579
		$imgData = file_get_contents(\OC::$SERVERROOT . '/core/img/logo.png');
580
		$storage->mkdir('folder');
581
		$storage->file_put_contents('foo.txt', $textData);
582
		$storage->file_put_contents('foo.png', $imgData);
583
		$storage->file_put_contents('folder/bar.txt', $textData);
584
585
		if ($scan) {
586
			$scanner = $storage->getScanner();
587
			$scanner->scan('');
588
		}
589
		$this->storages[] = $storage;
590
		return $storage;
591
	}
592
593
	/**
594
	 * @medium
595
	 */
596
	public function testViewHooksIfRootStartsTheSame() {
597
		$storage1 = $this->getTestStorage();
598
		$storage2 = $this->getTestStorage();
599
		$defaultRoot = \OC\Files\Filesystem::getRoot();
600
		\OC\Files\Filesystem::mount($storage1, array(), '/');
601
		\OC\Files\Filesystem::mount($storage2, array(), $defaultRoot . '_substorage');
602
		\OC_Hook::connect('OC_Filesystem', 'post_write', $this, 'dummyHook');
603
604
		$subView = new \OC\Files\View($defaultRoot . '_substorage');
605
		$this->hookPath = null;
606
607
		$subView->file_put_contents('/foo.txt', 'asd');
608
		$this->assertNull($this->hookPath);
609
	}
610
611
	private $hookWritePath;
612
	private $hookCreatePath;
613
	private $hookUpdatePath;
614
615
	public function dummyHookWrite($params) {
616
		$this->hookWritePath = $params['path'];
617
	}
618
619
	public function dummyHookUpdate($params) {
620
		$this->hookUpdatePath = $params['path'];
621
	}
622
623
	public function dummyHookCreate($params) {
624
		$this->hookCreatePath = $params['path'];
625
	}
626
627
	public function testEditNoCreateHook() {
628
		$storage1 = $this->getTestStorage();
629
		$storage2 = $this->getTestStorage();
630
		$defaultRoot = \OC\Files\Filesystem::getRoot();
631
		\OC\Files\Filesystem::mount($storage1, array(), '/');
632
		\OC\Files\Filesystem::mount($storage2, array(), $defaultRoot);
633
		\OC_Hook::connect('OC_Filesystem', 'post_create', $this, 'dummyHookCreate');
634
		\OC_Hook::connect('OC_Filesystem', 'post_update', $this, 'dummyHookUpdate');
635
		\OC_Hook::connect('OC_Filesystem', 'post_write', $this, 'dummyHookWrite');
636
637
		$view = new \OC\Files\View($defaultRoot);
638
		$this->hookWritePath = $this->hookUpdatePath = $this->hookCreatePath = null;
639
640
		$view->file_put_contents('/asd.txt', 'foo');
641
		$this->assertEquals('/asd.txt', $this->hookCreatePath);
642
		$this->assertNull($this->hookUpdatePath);
643
		$this->assertEquals('/asd.txt', $this->hookWritePath);
644
645
		$this->hookWritePath = $this->hookUpdatePath = $this->hookCreatePath = null;
646
647
		$view->file_put_contents('/asd.txt', 'foo');
648
		$this->assertNull($this->hookCreatePath);
649
		$this->assertEquals('/asd.txt', $this->hookUpdatePath);
650
		$this->assertEquals('/asd.txt', $this->hookWritePath);
651
652
		\OC_Hook::clear('OC_Filesystem', 'post_create');
653
		\OC_Hook::clear('OC_Filesystem', 'post_update');
654
		\OC_Hook::clear('OC_Filesystem', 'post_write');
655
	}
656
657
	/**
658
	 * @dataProvider resolvePathTestProvider
659
	 */
660
	public function testResolvePath($expected, $pathToTest) {
661
		$storage1 = $this->getTestStorage();
662
		\OC\Files\Filesystem::mount($storage1, array(), '/');
663
664
		$view = new \OC\Files\View('');
665
666
		$result = $view->resolvePath($pathToTest);
667
		$this->assertEquals($expected, $result[1]);
668
669
		$exists = $view->file_exists($pathToTest);
670
		$this->assertTrue($exists);
671
672
		$exists = $view->file_exists($result[1]);
673
		$this->assertTrue($exists);
674
	}
675
676
	function resolvePathTestProvider() {
677
		return array(
678
			array('foo.txt', 'foo.txt'),
679
			array('foo.txt', '/foo.txt'),
680
			array('folder', 'folder'),
681
			array('folder', '/folder'),
682
			array('folder', 'folder/'),
683
			array('folder', '/folder/'),
684
			array('folder/bar.txt', 'folder/bar.txt'),
685
			array('folder/bar.txt', '/folder/bar.txt'),
686
			array('', ''),
687
			array('', '/'),
688
		);
689
	}
690
691
	public function testUTF8Names() {
692
		$names = array('虚', '和知しゃ和で', 'regular ascii', 'sɨˈrɪlɪk', 'ѨѬ', 'أنا أحب القراءة كثيرا');
693
694
		$storage = new \OC\Files\Storage\Temporary(array());
695
		\OC\Files\Filesystem::mount($storage, array(), '/');
696
697
		$rootView = new \OC\Files\View('');
698
		foreach ($names as $name) {
699
			$rootView->file_put_contents('/' . $name, 'dummy content');
700
		}
701
702
		$list = $rootView->getDirectoryContent('/');
703
704
		$this->assertCount(count($names), $list);
705
		foreach ($list as $item) {
706
			$this->assertContains($item['name'], $names);
707
		}
708
709
		$cache = $storage->getCache();
710
		$scanner = $storage->getScanner();
711
		$scanner->scan('');
712
713
		$list = $cache->getFolderContents('');
714
715
		$this->assertCount(count($names), $list);
716
		foreach ($list as $item) {
717
			$this->assertContains($item['name'], $names);
718
		}
719
	}
720
721
	public function xtestLongPath() {
722
723
		$storage = new \OC\Files\Storage\Temporary(array());
724
		\OC\Files\Filesystem::mount($storage, array(), '/');
725
726
		$rootView = new \OC\Files\View('');
727
728
		$longPath = '';
729
		$ds = DIRECTORY_SEPARATOR;
730
		/*
731
		 * 4096 is the maximum path length in file_cache.path in *nix
732
		 * 1024 is the max path length in mac
733
		 * 228 is the max path length in windows
734
		 */
735
		$folderName = 'abcdefghijklmnopqrstuvwxyz012345678901234567890123456789';
736
		$tmpdirLength = strlen(\OC_Helper::tmpFolder());
737
		if (\OC_Util::runningOnWindows()) {
738
			$this->markTestSkipped('[Windows] ');
739
			$depth = ((260 - $tmpdirLength) / 57);
740
		} elseif (\OC_Util::runningOnMac()) {
741
			$depth = ((1024 - $tmpdirLength) / 57);
742
		} else {
743
			$depth = ((4000 - $tmpdirLength) / 57);
744
		}
745
		foreach (range(0, $depth - 1) as $i) {
746
			$longPath .= $ds . $folderName;
747
			$result = $rootView->mkdir($longPath);
748
			$this->assertTrue($result, "mkdir failed on $i - path length: " . strlen($longPath));
749
750
			$result = $rootView->file_put_contents($longPath . "{$ds}test.txt", 'lorem');
751
			$this->assertEquals(5, $result, "file_put_contents failed on $i");
752
753
			$this->assertTrue($rootView->file_exists($longPath));
754
			$this->assertTrue($rootView->file_exists($longPath . "{$ds}test.txt"));
755
		}
756
757
		$cache = $storage->getCache();
758
		$scanner = $storage->getScanner();
759
		$scanner->scan('');
760
761
		$longPath = $folderName;
762
		foreach (range(0, $depth - 1) as $i) {
763
			$cachedFolder = $cache->get($longPath);
764
			$this->assertTrue(is_array($cachedFolder), "No cache entry for folder at $i");
765
			$this->assertEquals($folderName, $cachedFolder['name'], "Wrong cache entry for folder at $i");
766
767
			$cachedFile = $cache->get($longPath . '/test.txt');
768
			$this->assertTrue(is_array($cachedFile), "No cache entry for file at $i");
769
			$this->assertEquals('test.txt', $cachedFile['name'], "Wrong cache entry for file at $i");
770
771
			$longPath .= $ds . $folderName;
772
		}
773
	}
774
775
	public function testTouchNotSupported() {
776
		$storage = new TemporaryNoTouch(array());
777
		$scanner = $storage->getScanner();
778
		\OC\Files\Filesystem::mount($storage, array(), '/test/');
779
		$past = time() - 100;
780
		$storage->file_put_contents('test', 'foobar');
781
		$scanner->scan('');
782
		$view = new \OC\Files\View('');
783
		$info = $view->getFileInfo('/test/test');
784
785
		$view->touch('/test/test', $past);
786
		$scanner->scanFile('test', \OC\Files\Cache\Scanner::REUSE_ETAG);
787
788
		$info2 = $view->getFileInfo('/test/test');
789
		$this->assertSame($info['etag'], $info2['etag']);
790
	}
791
792
	public function testWatcherEtagCrossStorage() {
793
		$storage1 = new Temporary(array());
794
		$storage2 = new Temporary(array());
795
		$scanner1 = $storage1->getScanner();
796
		$scanner2 = $storage2->getScanner();
797
		$storage1->mkdir('sub');
798
		\OC\Files\Filesystem::mount($storage1, array(), '/test/');
799
		\OC\Files\Filesystem::mount($storage2, array(), '/test/sub/storage');
800
801
		$past = time() - 100;
802
		$storage2->file_put_contents('test.txt', 'foobar');
803
		$scanner1->scan('');
804
		$scanner2->scan('');
805
		$view = new \OC\Files\View('');
806
807
		$storage2->getWatcher('')->setPolicy(Watcher::CHECK_ALWAYS);
808
809
		$oldFileInfo = $view->getFileInfo('/test/sub/storage/test.txt');
810
		$oldFolderInfo = $view->getFileInfo('/test');
811
812
		$storage2->getCache()->update($oldFileInfo->getId(), array(
813
			'storage_mtime' => $past
814
		));
815
816
		$view->getFileInfo('/test/sub/storage/test.txt');
817
		$newFolderInfo = $view->getFileInfo('/test');
818
819
		$this->assertNotEquals($newFolderInfo->getEtag(), $oldFolderInfo->getEtag());
820
	}
821
822
	/**
823
	 * @dataProvider absolutePathProvider
824
	 */
825
	public function testGetAbsolutePath($expectedPath, $relativePath) {
826
		$view = new \OC\Files\View('/files');
827
		$this->assertEquals($expectedPath, $view->getAbsolutePath($relativePath));
828
	}
829
830
	public function testPartFileInfo() {
831
		$storage = new Temporary(array());
832
		$scanner = $storage->getScanner();
833
		\OC\Files\Filesystem::mount($storage, array(), '/test/');
834
		$storage->file_put_contents('test.part', 'foobar');
835
		$scanner->scan('');
836
		$view = new \OC\Files\View('/test');
837
		$info = $view->getFileInfo('test.part');
838
839
		$this->assertInstanceOf('\OCP\Files\FileInfo', $info);
840
		$this->assertNull($info->getId());
841
		$this->assertEquals(6, $info->getSize());
842
	}
843
844
	function absolutePathProvider() {
845
		return array(
846
			array('/files/', ''),
847
			array('/files/0', '0'),
848
			array('/files/false', 'false'),
849
			array('/files/true', 'true'),
850
			array('/files/', '/'),
851
			array('/files/test', 'test'),
852
			array('/files/test', '/test'),
853
		);
854
	}
855
856
	/**
857
	 * @dataProvider chrootRelativePathProvider
858
	 */
859
	function testChrootGetRelativePath($root, $absolutePath, $expectedPath) {
860
		$view = new \OC\Files\View('/files');
861
		$view->chroot($root);
862
		$this->assertEquals($expectedPath, $view->getRelativePath($absolutePath));
863
	}
864
865
	public function chrootRelativePathProvider() {
866
		return $this->relativePathProvider('/');
867
	}
868
869
	/**
870
	 * @dataProvider initRelativePathProvider
871
	 */
872
	public function testInitGetRelativePath($root, $absolutePath, $expectedPath) {
873
		$view = new \OC\Files\View($root);
874
		$this->assertEquals($expectedPath, $view->getRelativePath($absolutePath));
875
	}
876
877
	public function initRelativePathProvider() {
878
		return $this->relativePathProvider(null);
879
	}
880
881
	public function relativePathProvider($missingRootExpectedPath) {
882
		return array(
883
			// No root - returns the path
884
			array('', '/files', '/files'),
885
			array('', '/files/', '/files/'),
886
887
			// Root equals path - /
888
			array('/files/', '/files/', '/'),
889
			array('/files/', '/files', '/'),
890
			array('/files', '/files/', '/'),
891
			array('/files', '/files', '/'),
892
893
			// False negatives: chroot fixes those by adding the leading slash.
894
			// But setting them up with this root (instead of chroot($root))
895
			// will fail them, although they should be the same.
896
			// TODO init should be fixed, so it also adds the leading slash
897
			array('files/', '/files/', $missingRootExpectedPath),
898
			array('files', '/files/', $missingRootExpectedPath),
899
			array('files/', '/files', $missingRootExpectedPath),
900
			array('files', '/files', $missingRootExpectedPath),
901
902
			// False negatives: Paths provided to the method should have a leading slash
903
			// TODO input should be checked to have a leading slash
904
			array('/files/', 'files/', null),
905
			array('/files', 'files/', null),
906
			array('/files/', 'files', null),
907
			array('/files', 'files', null),
908
909
			// with trailing slashes
910
			array('/files/', '/files/0', '0'),
911
			array('/files/', '/files/false', 'false'),
912
			array('/files/', '/files/true', 'true'),
913
			array('/files/', '/files/test', 'test'),
914
			array('/files/', '/files/test/foo', 'test/foo'),
915
916
			// without trailing slashes
917
			// TODO false expectation: Should match "with trailing slashes"
918
			array('/files', '/files/0', '/0'),
919
			array('/files', '/files/false', '/false'),
920
			array('/files', '/files/true', '/true'),
921
			array('/files', '/files/test', '/test'),
922
			array('/files', '/files/test/foo', '/test/foo'),
923
924
			// leading slashes
925
			array('/files/', '/files_trashbin/', null),
926
			array('/files', '/files_trashbin/', null),
927
			array('/files/', '/files_trashbin', null),
928
			array('/files', '/files_trashbin', null),
929
930
			// no leading slashes
931
			array('files/', 'files_trashbin/', null),
932
			array('files', 'files_trashbin/', null),
933
			array('files/', 'files_trashbin', null),
934
			array('files', 'files_trashbin', null),
935
936
			// mixed leading slashes
937
			array('files/', '/files_trashbin/', null),
938
			array('/files/', 'files_trashbin/', null),
939
			array('files', '/files_trashbin/', null),
940
			array('/files', 'files_trashbin/', null),
941
			array('files/', '/files_trashbin', null),
942
			array('/files/', 'files_trashbin', null),
943
			array('files', '/files_trashbin', null),
944
			array('/files', 'files_trashbin', null),
945
946
			array('files', 'files_trashbin/test', null),
947
			array('/files', '/files_trashbin/test', null),
948
			array('/files', 'files_trashbin/test', null),
949
		);
950
	}
951
952
	public function testFileView() {
953
		$storage = new Temporary(array());
954
		$scanner = $storage->getScanner();
955
		$storage->file_put_contents('foo.txt', 'bar');
956
		\OC\Files\Filesystem::mount($storage, array(), '/test/');
957
		$scanner->scan('');
958
		$view = new \OC\Files\View('/test/foo.txt');
959
960
		$this->assertEquals('bar', $view->file_get_contents(''));
961
		$fh = tmpfile();
962
		fwrite($fh, 'foo');
963
		rewind($fh);
964
		$view->file_put_contents('', $fh);
965
		$this->assertEquals('foo', $view->file_get_contents(''));
966
	}
967
968
	/**
969
	 * @dataProvider tooLongPathDataProvider
970
	 * @expectedException \OCP\Files\InvalidPathException
971
	 */
972
	public function testTooLongPath($operation, $param0 = null) {
973
974
		$longPath = '';
975
		// 4000 is the maximum path length in file_cache.path
976
		$folderName = 'abcdefghijklmnopqrstuvwxyz012345678901234567890123456789';
977
		$depth = (4000 / 57);
978
		foreach (range(0, $depth + 1) as $i) {
979
			$longPath .= '/' . $folderName;
980
		}
981
982
		$storage = new \OC\Files\Storage\Temporary(array());
983
		$this->tempStorage = $storage; // for later hard cleanup
984
		\OC\Files\Filesystem::mount($storage, array(), '/');
985
986
		$rootView = new \OC\Files\View('');
987
988
		if ($param0 === '@0') {
989
			$param0 = $longPath;
990
		}
991
992
		if ($operation === 'hash') {
993
			$param0 = $longPath;
994
			$longPath = 'md5';
995
		}
996
997
		call_user_func(array($rootView, $operation), $longPath, $param0);
998
	}
999
1000
	public function tooLongPathDataProvider() {
1001
		return array(
1002
			array('getAbsolutePath'),
1003
			array('getRelativePath'),
1004
			array('getMountPoint'),
1005
			array('resolvePath'),
1006
			array('getLocalFile'),
1007
			array('getLocalFolder'),
1008
			array('mkdir'),
1009
			array('rmdir'),
1010
			array('opendir'),
1011
			array('is_dir'),
1012
			array('is_file'),
1013
			array('stat'),
1014
			array('filetype'),
1015
			array('filesize'),
1016
			array('readfile'),
1017
			array('isCreatable'),
1018
			array('isReadable'),
1019
			array('isUpdatable'),
1020
			array('isDeletable'),
1021
			array('isSharable'),
1022
			array('file_exists'),
1023
			array('filemtime'),
1024
			array('touch'),
1025
			array('file_get_contents'),
1026
			array('unlink'),
1027
			array('deleteAll'),
1028
			array('toTmpFile'),
1029
			array('getMimeType'),
1030
			array('free_space'),
1031
			array('getFileInfo'),
1032
			array('getDirectoryContent'),
1033
			array('getOwner'),
1034
			array('getETag'),
1035
			array('file_put_contents', 'ipsum'),
1036
			array('rename', '@0'),
1037
			array('copy', '@0'),
1038
			array('fopen', 'r'),
1039
			array('fromTmpFile', '@0'),
1040
			array('hash'),
1041
			array('hasUpdated', 0),
1042
			array('putFileInfo', array()),
1043
		);
1044
	}
1045
1046
	public function testRenameCrossStoragePreserveMtime() {
1047
		$storage1 = new Temporary(array());
1048
		$storage2 = new Temporary(array());
1049
		$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...
1050
		$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...
1051
		$storage1->mkdir('sub');
1052
		$storage1->mkdir('foo');
1053
		$storage1->file_put_contents('foo.txt', 'asd');
1054
		$storage1->file_put_contents('foo/bar.txt', 'asd');
1055
		\OC\Files\Filesystem::mount($storage1, array(), '/test/');
1056
		\OC\Files\Filesystem::mount($storage2, array(), '/test/sub/storage');
1057
1058
		$view = new \OC\Files\View('');
1059
		$time = time() - 200;
1060
		$view->touch('/test/foo.txt', $time);
1061
		$view->touch('/test/foo', $time);
1062
		$view->touch('/test/foo/bar.txt', $time);
1063
1064
		$view->rename('/test/foo.txt', '/test/sub/storage/foo.txt');
1065
1066
		$this->assertEquals($time, $view->filemtime('/test/sub/storage/foo.txt'));
1067
1068
		$view->rename('/test/foo', '/test/sub/storage/foo');
1069
1070
		$this->assertEquals($time, $view->filemtime('/test/sub/storage/foo/bar.txt'));
1071
	}
1072
1073
	public function testRenameFailDeleteTargetKeepSource() {
1074
		$this->doTestCopyRenameFail('rename');
1075
	}
1076
1077
	public function testCopyFailDeleteTargetKeepSource() {
1078
		$this->doTestCopyRenameFail('copy');
1079
	}
1080
1081
	private function doTestCopyRenameFail($operation) {
1082
		$storage1 = new Temporary(array());
1083
		/** @var \PHPUnit_Framework_MockObject_MockObject | \OC\Files\Storage\Temporary $storage2 */
1084
		$storage2 = $this->getMockBuilder('\Test\Files\TemporaryNoCross')
1085
			->setConstructorArgs([[]])
1086
			->setMethods(['fopen'])
1087
			->getMock();
1088
1089
		$storage2->expects($this->any())
1090
			->method('fopen')
1091
			->will($this->returnCallback(function ($path, $mode) use ($storage2) {
1092
				/** @var \PHPUnit_Framework_MockObject_MockObject | \OC\Files\Storage\Temporary $storage2 */
1093
				$source = fopen($storage2->getSourcePath($path), $mode);
1094
				return \OC\Files\Stream\Quota::wrap($source, 9);
1095
			}));
1096
1097
		$storage1->mkdir('sub');
1098
		$storage1->file_put_contents('foo.txt', '0123456789ABCDEFGH');
1099
		$storage1->mkdir('dirtomove');
1100
		$storage1->file_put_contents('dirtomove/indir1.txt', '0123456'); // fits
1101
		$storage1->file_put_contents('dirtomove/indir2.txt', '0123456789ABCDEFGH'); // doesn't fit
1102
		$storage2->file_put_contents('existing.txt', '0123');
1103
		$storage1->getScanner()->scan('');
1104
		$storage2->getScanner()->scan('');
1105
		\OC\Files\Filesystem::mount($storage1, array(), '/test/');
1106
		\OC\Files\Filesystem::mount($storage2, array(), '/test/sub/storage');
1107
1108
		// move file
1109
		$view = new \OC\Files\View('');
1110
		$this->assertTrue($storage1->file_exists('foo.txt'));
1111
		$this->assertFalse($storage2->file_exists('foo.txt'));
1112
		$this->assertFalse($view->$operation('/test/foo.txt', '/test/sub/storage/foo.txt'));
1113
		$this->assertFalse($storage2->file_exists('foo.txt'));
1114
		$this->assertFalse($storage2->getCache()->get('foo.txt'));
1115
		$this->assertTrue($storage1->file_exists('foo.txt'));
1116
1117
		// if target exists, it will be deleted too
1118
		$this->assertFalse($view->$operation('/test/foo.txt', '/test/sub/storage/existing.txt'));
1119
		$this->assertFalse($storage2->file_exists('existing.txt'));
1120
		$this->assertFalse($storage2->getCache()->get('existing.txt'));
1121
		$this->assertTrue($storage1->file_exists('foo.txt'));
1122
1123
		// move folder
1124
		$this->assertFalse($view->$operation('/test/dirtomove/', '/test/sub/storage/dirtomove/'));
1125
		// since the move failed, the full source tree is kept
1126
		$this->assertTrue($storage1->file_exists('dirtomove/indir1.txt'));
1127
		$this->assertTrue($storage1->file_exists('dirtomove/indir2.txt'));
1128
		// second file not moved/copied
1129
		$this->assertFalse($storage2->file_exists('dirtomove/indir2.txt'));
1130
		$this->assertFalse($storage2->getCache()->get('dirtomove/indir2.txt'));
1131
1132
	}
1133
1134
	public function testDeleteFailKeepCache() {
1135
		/**
1136
		 * @var \PHPUnit_Framework_MockObject_MockObject | \OC\Files\Storage\Temporary $storage
1137
		 */
1138
		$storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
1139
			->setConstructorArgs(array(array()))
1140
			->setMethods(array('unlink'))
1141
			->getMock();
1142
		$storage->expects($this->once())
1143
			->method('unlink')
1144
			->will($this->returnValue(false));
1145
		$scanner = $storage->getScanner();
1146
		$cache = $storage->getCache();
1147
		$storage->file_put_contents('foo.txt', 'asd');
1148
		$scanner->scan('');
1149
		\OC\Files\Filesystem::mount($storage, array(), '/test/');
1150
1151
		$view = new \OC\Files\View('/test');
1152
1153
		$this->assertFalse($view->unlink('foo.txt'));
1154
		$this->assertTrue($cache->inCache('foo.txt'));
1155
	}
1156
1157
	function directoryTraversalProvider() {
1158
		return [
1159
			['../test/'],
1160
			['..\\test\\my/../folder'],
1161
			['/test/my/../foo\\'],
1162
		];
1163
	}
1164
1165
	/**
1166
	 * @dataProvider directoryTraversalProvider
1167
	 * @expectedException \Exception
1168
	 * @param string $root
1169
	 */
1170
	public function testConstructDirectoryTraversalException($root) {
1171
		new \OC\Files\View($root);
1172
	}
1173
1174
	public function testRenameOverWrite() {
1175
		$storage = new Temporary(array());
1176
		$scanner = $storage->getScanner();
1177
		$storage->mkdir('sub');
1178
		$storage->mkdir('foo');
1179
		$storage->file_put_contents('foo.txt', 'asd');
1180
		$storage->file_put_contents('foo/bar.txt', 'asd');
1181
		$scanner->scan('');
1182
		\OC\Files\Filesystem::mount($storage, array(), '/test/');
1183
		$view = new \OC\Files\View('');
1184
		$this->assertTrue($view->rename('/test/foo.txt', '/test/foo/bar.txt'));
1185
	}
1186
1187
	public function testSetMountOptionsInStorage() {
1188
		$mount = new MountPoint('\OC\Files\Storage\Temporary', '/asd/', [[]], \OC\Files\Filesystem::getLoader(), ['foo' => 'bar']);
1189
		\OC\Files\Filesystem::getMountManager()->addMount($mount);
1190
		/** @var \OC\Files\Storage\Common $storage */
1191
		$storage = $mount->getStorage();
1192
		$this->assertEquals($storage->getMountOption('foo'), 'bar');
1193
	}
1194
1195
	public function testSetMountOptionsWatcherPolicy() {
1196
		$mount = new MountPoint('\OC\Files\Storage\Temporary', '/asd/', [[]], \OC\Files\Filesystem::getLoader(), ['filesystem_check_changes' => Watcher::CHECK_NEVER]);
1197
		\OC\Files\Filesystem::getMountManager()->addMount($mount);
1198
		/** @var \OC\Files\Storage\Common $storage */
1199
		$storage = $mount->getStorage();
1200
		$watcher = $storage->getWatcher();
1201
		$this->assertEquals(Watcher::CHECK_NEVER, $watcher->getPolicy());
1202
	}
1203
1204
	public function testGetAbsolutePathOnNull() {
1205
		$view = new \OC\Files\View();
1206
		$this->assertNull($view->getAbsolutePath(null));
1207
	}
1208
1209
	public function testGetRelativePathOnNull() {
1210
		$view = new \OC\Files\View();
1211
		$this->assertNull($view->getRelativePath(null));
1212
	}
1213
1214
	/**
1215
	 * @expectedException \InvalidArgumentException
1216
	 */
1217
	public function testNullAsRoot() {
1218
		new \OC\Files\View(null);
1219
	}
1220
1221
	/**
1222
	 * e.g. reading from a folder that's being renamed
1223
	 *
1224
	 * @expectedException \OCP\Lock\LockedException
1225
	 *
1226
	 * @dataProvider dataLockPaths
1227
	 *
1228
	 * @param string $rootPath
1229
	 * @param string $pathPrefix
1230
	 */
1231 View Code Duplication
	public function testReadFromWriteLockedPath($rootPath, $pathPrefix) {
1232
		$rootPath = str_replace('{folder}', 'files', $rootPath);
1233
		$pathPrefix = str_replace('{folder}', 'files', $pathPrefix);
1234
1235
		$view = new \OC\Files\View($rootPath);
1236
		$storage = new Temporary(array());
1237
		\OC\Files\Filesystem::mount($storage, [], '/');
1238
		$this->assertTrue($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_EXCLUSIVE));
1239
		$view->lockFile($pathPrefix . '/foo/bar/asd', ILockingProvider::LOCK_SHARED);
1240
	}
1241
1242
	/**
1243
	 * Reading from a files_encryption folder that's being renamed
1244
	 *
1245
	 * @dataProvider dataLockPaths
1246
	 *
1247
	 * @param string $rootPath
1248
	 * @param string $pathPrefix
1249
	 */
1250 View Code Duplication
	public function testReadFromWriteUnlockablePath($rootPath, $pathPrefix) {
1251
		$rootPath = str_replace('{folder}', 'files_encryption', $rootPath);
1252
		$pathPrefix = str_replace('{folder}', 'files_encryption', $pathPrefix);
1253
1254
		$view = new \OC\Files\View($rootPath);
1255
		$storage = new Temporary(array());
1256
		\OC\Files\Filesystem::mount($storage, [], '/');
1257
		$this->assertFalse($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_EXCLUSIVE));
1258
		$this->assertFalse($view->lockFile($pathPrefix . '/foo/bar/asd', ILockingProvider::LOCK_SHARED));
1259
	}
1260
1261
	/**
1262
	 * e.g. writing a file that's being downloaded
1263
	 *
1264
	 * @expectedException \OCP\Lock\LockedException
1265
	 *
1266
	 * @dataProvider dataLockPaths
1267
	 *
1268
	 * @param string $rootPath
1269
	 * @param string $pathPrefix
1270
	 */
1271 View Code Duplication
	public function testWriteToReadLockedFile($rootPath, $pathPrefix) {
1272
		$rootPath = str_replace('{folder}', 'files', $rootPath);
1273
		$pathPrefix = str_replace('{folder}', 'files', $pathPrefix);
1274
1275
		$view = new \OC\Files\View($rootPath);
1276
		$storage = new Temporary(array());
1277
		\OC\Files\Filesystem::mount($storage, [], '/');
1278
		$this->assertTrue($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_SHARED));
1279
		$view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_EXCLUSIVE);
1280
	}
1281
1282
	/**
1283
	 * Writing a file that's being downloaded
1284
	 *
1285
	 * @dataProvider dataLockPaths
1286
	 *
1287
	 * @param string $rootPath
1288
	 * @param string $pathPrefix
1289
	 */
1290 View Code Duplication
	public function testWriteToReadUnlockableFile($rootPath, $pathPrefix) {
1291
		$rootPath = str_replace('{folder}', 'files_encryption', $rootPath);
1292
		$pathPrefix = str_replace('{folder}', 'files_encryption', $pathPrefix);
1293
1294
		$view = new \OC\Files\View($rootPath);
1295
		$storage = new Temporary(array());
1296
		\OC\Files\Filesystem::mount($storage, [], '/');
1297
		$this->assertFalse($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_SHARED));
1298
		$this->assertFalse($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_EXCLUSIVE));
1299
	}
1300
1301
	/**
1302
	 * Test that locks are on mount point paths instead of mount root
1303
	 */
1304 View Code Duplication
	public function testLockLocalMountPointPathInsteadOfStorageRoot() {
1305
		$lockingProvider = \OC::$server->getLockingProvider();
1306
		$view = new \OC\Files\View('/testuser/files/');
1307
		$storage = new Temporary([]);
1308
		\OC\Files\Filesystem::mount($storage, [], '/');
1309
		$mountedStorage = new Temporary([]);
1310
		\OC\Files\Filesystem::mount($mountedStorage, [], '/testuser/files/mountpoint');
1311
1312
		$this->assertTrue(
1313
			$view->lockFile('/mountpoint', ILockingProvider::LOCK_EXCLUSIVE, true),
1314
			'Can lock mount point'
1315
		);
1316
1317
		// no exception here because storage root was not locked
1318
		$mountedStorage->acquireLock('', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
1319
1320
		$thrown = false;
1321
		try {
1322
			$storage->acquireLock('/testuser/files/mountpoint', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
1323
		} catch (\OCP\Lock\LockedException $e) {
1324
			$thrown = true;
1325
		}
1326
		$this->assertTrue($thrown, 'Mount point path was locked on root storage');
1327
1328
		$lockingProvider->releaseAll();
1329
	}
1330
1331
	/**
1332
	 * Test that locks are on mount point paths and also mount root when requested
1333
	 */
1334 View Code Duplication
	public function testLockStorageRootButNotLocalMountPoint() {
1335
		$lockingProvider = \OC::$server->getLockingProvider();
1336
		$view = new \OC\Files\View('/testuser/files/');
1337
		$storage = new Temporary([]);
1338
		\OC\Files\Filesystem::mount($storage, [], '/');
1339
		$mountedStorage = new Temporary([]);
1340
		\OC\Files\Filesystem::mount($mountedStorage, [], '/testuser/files/mountpoint');
1341
1342
		$this->assertTrue(
1343
			$view->lockFile('/mountpoint', ILockingProvider::LOCK_EXCLUSIVE, false),
1344
			'Can lock mount point'
1345
		);
1346
1347
		$thrown = false;
1348
		try {
1349
			$mountedStorage->acquireLock('', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
1350
		} catch (\OCP\Lock\LockedException $e) {
1351
			$thrown = true;
1352
		}
1353
		$this->assertTrue($thrown, 'Mount point storage root was locked on original storage');
1354
1355
		// local mount point was not locked
1356
		$storage->acquireLock('/testuser/files/mountpoint', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
1357
1358
		$lockingProvider->releaseAll();
1359
	}
1360
1361
	/**
1362
	 * Test that locks are on mount point paths and also mount root when requested
1363
	 */
1364
	public function testLockMountPointPathFailReleasesBoth() {
1365
		$lockingProvider = \OC::$server->getLockingProvider();
1366
		$view = new \OC\Files\View('/testuser/files/');
1367
		$storage = new Temporary([]);
1368
		\OC\Files\Filesystem::mount($storage, [], '/');
1369
		$mountedStorage = new Temporary([]);
1370
		\OC\Files\Filesystem::mount($mountedStorage, [], '/testuser/files/mountpoint.txt');
1371
1372
		// this would happen if someone is writing on the mount point
1373
		$mountedStorage->acquireLock('', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
1374
1375
		$thrown = false;
1376
		try {
1377
			// this actually acquires two locks, one on the mount point and one on the storage root,
1378
			// but the one on the storage root will fail
1379
			$view->lockFile('/mountpoint.txt', ILockingProvider::LOCK_SHARED);
1380
		} catch (\OCP\Lock\LockedException $e) {
1381
			$thrown = true;
1382
		}
1383
		$this->assertTrue($thrown, 'Cannot acquire shared lock because storage root is already locked');
1384
1385
		// from here we expect that the lock on the local mount point was released properly
1386
		// so acquiring an exclusive lock will succeed
1387
		$storage->acquireLock('/testuser/files/mountpoint.txt', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
1388
1389
		$lockingProvider->releaseAll();
1390
	}
1391
1392 View Code Duplication
	public function dataLockPaths() {
1393
		return [
1394
			['/testuser/{folder}', ''],
1395
			['/testuser', '/{folder}'],
1396
			['', '/testuser/{folder}'],
1397
		];
1398
	}
1399
1400
	public function pathRelativeToFilesProvider() {
1401
		return [
1402
			['admin/files', ''],
1403
			['admin/files/x', 'x'],
1404
			['/admin/files', ''],
1405
			['/admin/files/sub', 'sub'],
1406
			['/admin/files/sub/', 'sub'],
1407
			['/admin/files/sub/sub2', 'sub/sub2'],
1408
			['//admin//files/sub//sub2', 'sub/sub2'],
1409
		];
1410
	}
1411
1412
	/**
1413
	 * @dataProvider pathRelativeToFilesProvider
1414
	 */
1415
	public function testGetPathRelativeToFiles($path, $expectedPath) {
1416
		$view = new \OC\Files\View();
1417
		$this->assertEquals($expectedPath, $view->getPathRelativeToFiles($path));
1418
	}
1419
1420
	public function pathRelativeToFilesProviderExceptionCases() {
1421
		return [
1422
			[''],
1423
			['x'],
1424
			['files'],
1425
			['/files'],
1426
			['/admin/files_versions/abc'],
1427
		];
1428
	}
1429
1430
	/**
1431
	 * @dataProvider pathRelativeToFilesProviderExceptionCases
1432
	 * @expectedException \InvalidArgumentException
1433
	 */
1434
	public function testGetPathRelativeToFilesWithInvalidArgument($path) {
1435
		$view = new \OC\Files\View();
1436
		$view->getPathRelativeToFiles($path);
1437
	}
1438
1439
	public function testChangeLock() {
1440
		$view = new \OC\Files\View('/testuser/files/');
1441
		$storage = new Temporary(array());
1442
		\OC\Files\Filesystem::mount($storage, [], '/');
1443
1444
		$view->lockFile('/test/sub', ILockingProvider::LOCK_SHARED);
1445
		$this->assertTrue($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_SHARED));
1446
		$this->assertFalse($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_EXCLUSIVE));
1447
1448
		$view->changeLock('//test/sub', ILockingProvider::LOCK_EXCLUSIVE);
1449
		$this->assertTrue($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_EXCLUSIVE));
1450
1451
		$view->changeLock('test/sub', ILockingProvider::LOCK_SHARED);
1452
		$this->assertTrue($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_SHARED));
1453
1454
		$view->unlockFile('/test/sub/', ILockingProvider::LOCK_SHARED);
1455
1456
		$this->assertFalse($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_SHARED));
1457
		$this->assertFalse($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_EXCLUSIVE));
1458
1459
	}
1460
1461
	public function hookPathProvider() {
1462
		return [
1463
			['/foo/files', '/foo', true],
1464
			['/foo/files/bar', '/foo', true],
1465
			['/foo', '/foo', false],
1466
			['/foo', '/files/foo', true],
1467
			['/foo', 'filesfoo', false],
1468
			['', '/foo/files', true],
1469
			['', '/foo/files/bar.txt', true]
1470
		];
1471
	}
1472
1473
	/**
1474
	 * @dataProvider hookPathProvider
1475
	 * @param $root
1476
	 * @param $path
1477
	 * @param $shouldEmit
1478
	 */
1479
	public function testHookPaths($root, $path, $shouldEmit) {
1480
		$filesystemReflection = new \ReflectionClass('\OC\Files\Filesystem');
1481
		$defaultRootValue = $filesystemReflection->getProperty('defaultInstance');
1482
		$defaultRootValue->setAccessible(true);
1483
		$oldRoot = $defaultRootValue->getValue();
1484
		$defaultView = new \OC\Files\View('/foo/files');
1485
		$defaultRootValue->setValue($defaultView);
1486
		$view = new \OC\Files\View($root);
1487
		$result = \Test_Helper::invokePrivate($view, 'shouldEmitHooks', [$path]);
1488
		$defaultRootValue->setValue($oldRoot);
1489
		$this->assertEquals($shouldEmit, $result);
1490
	}
1491
1492
	/**
1493
	 * Create test movable mount points
1494
	 *
1495
	 * @param array $mountPoints array of mount point locations
1496
	 * @return array array of MountPoint objects
1497
	 */
1498
	private function createTestMovableMountPoints($mountPoints) {
1499
		$mounts = [];
1500
		foreach ($mountPoints as $mountPoint) {
1501
			$storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
1502
				->setMethods([])
1503
				->getMock();
1504
1505
			$mounts[] = $this->getMock(
1506
				'\Test\TestMoveableMountPoint',
1507
				['moveMount'],
1508
				[$storage, $mountPoint]
1509
			);
1510
		}
1511
1512
		$mountProvider = $this->getMock('\OCP\Files\Config\IMountProvider');
1513
		$mountProvider->expects($this->any())
1514
			->method('getMountsForUser')
1515
			->will($this->returnValue($mounts));
1516
1517
		$mountProviderCollection = \OC::$server->getMountProviderCollection();
1518
		$mountProviderCollection->registerProvider($mountProvider);
1519
1520
		return $mounts;
1521
	}
1522
1523
	/**
1524
	 * Test mount point move
1525
	 */
1526
	public function testMountPointMove() {
1527
		$this->loginAsUser($this->user);
1528
1529
		list($mount1, $mount2) = $this->createTestMovableMountPoints([
1530
			$this->user . '/files/mount1',
1531
			$this->user . '/files/mount2',
1532
		]);
1533
		$mount1->expects($this->once())
1534
			->method('moveMount')
1535
			->will($this->returnValue(true));
1536
1537
		$mount2->expects($this->once())
1538
			->method('moveMount')
1539
			->will($this->returnValue(true));
1540
1541
		$view = new \OC\Files\View('/' . $this->user . '/files/');
1542
		$view->mkdir('sub');
1543
1544
		$this->assertTrue($view->rename('mount1', 'renamed_mount'), 'Can rename mount point');
1545
		$this->assertTrue($view->rename('mount2', 'sub/moved_mount'), 'Can move a mount point into a subdirectory');
1546
	}
1547
	/**
1548
	 * Test that moving a mount point into another is forbidden
1549
	 */
1550
	public function testMoveMountPointIntoAnother() {
1551
		$this->loginAsUser($this->user);
1552
1553
		list($mount1, $mount2) = $this->createTestMovableMountPoints([
1554
			$this->user . '/files/mount1',
1555
			$this->user . '/files/mount2',
1556
		]);
1557
1558
		$mount1->expects($this->never())
1559
			->method('moveMount');
1560
1561
		$mount2->expects($this->never())
1562
			->method('moveMount');
1563
1564
		$view = new \OC\Files\View('/' . $this->user . '/files/');
1565
1566
		$this->assertFalse($view->rename('mount1', 'mount2'), 'Cannot overwrite another mount point');
1567
		$this->assertFalse($view->rename('mount1', 'mount2/sub'), 'Cannot move a mount point into another');
1568
	}
1569
	/**
1570
	 * Test that moving a mount point into a shared folder is forbidden
1571
	 */
1572
	public function testMoveMountPointIntoSharedFolder() {
1573
		$this->loginAsUser($this->user);
1574
1575
		list($mount1) = $this->createTestMovableMountPoints([
1576
			$this->user . '/files/mount1',
1577
		]);
1578
1579
		$mount1->expects($this->never())
1580
			->method('moveMount');
1581
1582
		$view = new \OC\Files\View('/' . $this->user . '/files/');
1583
		$view->mkdir('shareddir');
1584
		$view->mkdir('shareddir/sub');
1585
		$view->mkdir('shareddir/sub2');
1586
1587
		$fileId = $view->getFileInfo('shareddir')->getId();
1588
		$userObject = \OC::$server->getUserManager()->createUser('test2', 'IHateNonMockableStaticClasses');
1589
		$this->assertTrue(\OCP\Share::shareItem('folder', $fileId, \OCP\Share::SHARE_TYPE_USER, 'test2', \OCP\Constants::PERMISSION_READ));
1590
1591
		$this->assertFalse($view->rename('mount1', 'shareddir'), 'Cannot overwrite shared folder');
1592
		$this->assertFalse($view->rename('mount1', 'shareddir/sub'), 'Cannot move mount point into shared folder');
1593
		$this->assertFalse($view->rename('mount1', 'shareddir/sub/sub2'), 'Cannot move mount point into shared subfolder');
1594
1595
		$this->assertTrue(\OCP\Share::unshare('folder', $fileId, \OCP\Share::SHARE_TYPE_USER, 'test2'));
1596
		$userObject->delete();
1597
	}
1598
1599
	public function basicOperationProviderForLocks() {
1600
		return [
1601
			// --- write hook ----
1602
			[
1603
				'touch',
1604
				['touch-create.txt'],
1605
				'touch-create.txt',
1606
				'create',
1607
				ILockingProvider::LOCK_SHARED,
1608
				ILockingProvider::LOCK_EXCLUSIVE,
1609
				ILockingProvider::LOCK_SHARED,
1610
			],
1611
			[
1612
				'fopen',
1613
				['test-write.txt', 'w'],
1614
				'test-write.txt',
1615
				'write',
1616
				ILockingProvider::LOCK_SHARED,
1617
				ILockingProvider::LOCK_EXCLUSIVE,
1618
				null,
1619
				// exclusive lock stays until fclose
1620
				ILockingProvider::LOCK_EXCLUSIVE,
1621
			],
1622
			[
1623
				'mkdir',
1624
				['newdir'],
1625
				'newdir',
1626
				'write',
1627
				ILockingProvider::LOCK_SHARED,
1628
				ILockingProvider::LOCK_EXCLUSIVE,
1629
				ILockingProvider::LOCK_SHARED,
1630
			],
1631
			[
1632
				'file_put_contents',
1633
				['file_put_contents.txt', 'blah'],
1634
				'file_put_contents.txt',
1635
				'write',
1636
				ILockingProvider::LOCK_SHARED,
1637
				ILockingProvider::LOCK_EXCLUSIVE,
1638
				ILockingProvider::LOCK_SHARED,
1639
			],
1640
1641
			// ---- delete hook ----
1642
			[
1643
				'rmdir',
1644
				['dir'],
1645
				'dir',
1646
				'delete',
1647
				ILockingProvider::LOCK_SHARED,
1648
				ILockingProvider::LOCK_EXCLUSIVE,
1649
				ILockingProvider::LOCK_SHARED,
1650
			],
1651
			[
1652
				'unlink',
1653
				['test.txt'],
1654
				'test.txt',
1655
				'delete',
1656
				ILockingProvider::LOCK_SHARED,
1657
				ILockingProvider::LOCK_EXCLUSIVE,
1658
				ILockingProvider::LOCK_SHARED,
1659
			],
1660
1661
			// ---- read hook (no post hooks) ----
1662
			[
1663
				'file_get_contents',
1664
				['test.txt'],
1665
				'test.txt',
1666
				'read',
1667
				ILockingProvider::LOCK_SHARED,
1668
				ILockingProvider::LOCK_SHARED,
1669
				null,
1670
			],
1671
			[
1672
				'fopen',
1673
				['test.txt', 'r'],
1674
				'test.txt',
1675
				'read',
1676
				ILockingProvider::LOCK_SHARED,
1677
				ILockingProvider::LOCK_SHARED,
1678
				null,
1679
			],
1680
			[
1681
				'opendir',
1682
				['dir'],
1683
				'dir',
1684
				'read',
1685
				ILockingProvider::LOCK_SHARED,
1686
				ILockingProvider::LOCK_SHARED,
1687
				null,
1688
			],
1689
1690
			// ---- no lock, touch hook ---
1691
			['touch', ['test.txt'], 'test.txt', 'touch', null, null, null],
1692
1693
			// ---- no hooks, no locks ---
1694
			['is_dir', ['dir'], 'dir', null],
1695
			['is_file', ['dir'], 'dir', null],
1696
			['stat', ['dir'], 'dir', null],
1697
			['filetype', ['dir'], 'dir', null],
1698
			['filesize', ['dir'], 'dir', null],
1699
			['isCreatable', ['dir'], 'dir', null],
1700
			['isReadable', ['dir'], 'dir', null],
1701
			['isUpdatable', ['dir'], 'dir', null],
1702
			['isDeletable', ['dir'], 'dir', null],
1703
			['isSharable', ['dir'], 'dir', null],
1704
			['file_exists', ['dir'], 'dir', null],
1705
			['filemtime', ['dir'], 'dir', null],
1706
		];
1707
	}
1708
1709
	/**
1710
	 * Test whether locks are set before and after the operation
1711
	 *
1712
	 * @dataProvider basicOperationProviderForLocks
1713
	 *
1714
	 * @param string $operation operation name on the view
1715
	 * @param array $operationArgs arguments for the operation
1716
	 * @param string $lockedPath path of the locked item to check
1717
	 * @param string $hookType hook type
1718
	 * @param int $expectedLockBefore expected lock during pre hooks
1719
	 * @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...
1720
	 * @param int $expectedLockAfter expected lock during post hooks
1721
	 * @param int $expectedStrayLock expected lock after returning, should
1722
	 * be null (unlock) for most operations
1723
	 */
1724
	public function testLockBasicOperation(
1725
		$operation,
1726
		$operationArgs,
1727
		$lockedPath,
1728
		$hookType,
1729
		$expectedLockBefore = ILockingProvider::LOCK_SHARED,
1730
		$expectedLockDuring = ILockingProvider::LOCK_SHARED,
1731
		$expectedLockAfter = ILockingProvider::LOCK_SHARED,
1732
		$expectedStrayLock = null
1733
	) {
1734
		$view = new \OC\Files\View('/' . $this->user . '/files/');
1735
1736
		$storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
1737
			->setMethods([$operation])
1738
			->getMock();
1739
1740
		\OC\Files\Filesystem::mount($storage, array(), $this->user . '/');
1741
1742
		// work directly on disk because mkdir might be mocked
1743
		$realPath = $storage->getSourcePath('');
1744
		mkdir($realPath . '/files');
1745
		mkdir($realPath . '/files/dir');
1746
		file_put_contents($realPath . '/files/test.txt', 'blah');
1747
		$storage->getScanner()->scan('files');
1748
1749
		$storage->expects($this->once())
1750
			->method($operation)
1751
			->will($this->returnCallback(
1752
				function() use ($view, $lockedPath, &$lockTypeDuring){
1753
					$lockTypeDuring = $this->getFileLockType($view, $lockedPath);
1754
1755
					return true;
1756
				}
1757
			));
1758
1759
		$this->assertNull($this->getFileLockType($view, $lockedPath), 'File not locked before operation');
1760
1761
		$this->connectMockHooks($hookType, $view, $lockedPath, $lockTypePre, $lockTypePost);
1762
1763
		// do operation
1764
		call_user_func_array(array($view, $operation), $operationArgs);
1765
1766
		if ($hookType !== null) {
1767
			$this->assertEquals($expectedLockBefore, $lockTypePre, 'File locked properly during pre-hook');
1768
			$this->assertEquals($expectedLockAfter, $lockTypePost, 'File locked properly during post-hook');
1769
			$this->assertEquals($expectedLockDuring, $lockTypeDuring, 'File locked properly during operation');
1770
		} else {
1771
			$this->assertNull($lockTypeDuring, 'File not locked during operation');
1772
		}
1773
1774
		$this->assertEquals($expectedStrayLock, $this->getFileLockType($view, $lockedPath));
1775
	}
1776
1777
	/**
1778
	 * Test locks for file_put_content with stream.
1779
	 * This code path uses $storage->fopen instead
1780
	 */
1781
	public function testLockFilePutContentWithStream() {
1782
		$view = new \OC\Files\View('/' . $this->user . '/files/');
1783
1784
		$path = 'test_file_put_contents.txt';
1785
		$storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
1786
			->setMethods(['fopen'])
1787
			->getMock();
1788
1789
		\OC\Files\Filesystem::mount($storage, array(), $this->user . '/');
1790
		$storage->mkdir('files');
1791
1792
		$storage->expects($this->once())
1793
			->method('fopen')
1794
			->will($this->returnCallback(
1795
				function() use ($view, $path, &$lockTypeDuring){
1796
					$lockTypeDuring = $this->getFileLockType($view, $path);
1797
1798
					return fopen('php://temp', 'r+');
1799
				}
1800
			));
1801
1802
		$this->connectMockHooks('write', $view, $path, $lockTypePre, $lockTypePost);
1803
1804
		$this->assertNull($this->getFileLockType($view, $path), 'File not locked before operation');
1805
1806
		// do operation
1807
		$view->file_put_contents($path, fopen('php://temp', 'r+'));
1808
1809
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypePre, 'File locked properly during pre-hook');
1810
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypePost, 'File locked properly during post-hook');
1811
		$this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeDuring, 'File locked properly during operation');
1812
1813
		$this->assertNull($this->getFileLockType($view, $path));
1814
	}
1815
1816
	/**
1817
	 * Test locks for fopen with fclose at the end
1818
	 */
1819
	public function testLockFopen() {
1820
		$view = new \OC\Files\View('/' . $this->user . '/files/');
1821
1822
		$path = 'test_file_put_contents.txt';
1823
		$storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
1824
			->setMethods(['fopen'])
1825
			->getMock();
1826
1827
		\OC\Files\Filesystem::mount($storage, array(), $this->user . '/');
1828
		$storage->mkdir('files');
1829
1830
		$storage->expects($this->once())
1831
			->method('fopen')
1832
			->will($this->returnCallback(
1833
				function() use ($view, $path, &$lockTypeDuring){
1834
					$lockTypeDuring = $this->getFileLockType($view, $path);
1835
1836
					return fopen('php://temp', 'r+');
1837
				}
1838
			));
1839
1840
		$this->connectMockHooks('write', $view, $path, $lockTypePre, $lockTypePost);
1841
1842
		$this->assertNull($this->getFileLockType($view, $path), 'File not locked before operation');
1843
1844
		// do operation
1845
		$res = $view->fopen($path, 'w');
1846
1847
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypePre, 'File locked properly during pre-hook');
1848
		$this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeDuring, 'File locked properly during operation');
1849
		$this->assertNull(null, $lockTypePost, 'No post hook, no lock check possible');
1850
1851
		$this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeDuring, 'File still locked after fopen');
1852
1853
		fclose($res);
1854
1855
		$this->assertNull($this->getFileLockType($view, $path), 'File unlocked after fclose');
1856
	}
1857
1858
	/**
1859
	 * Test locks for fopen with fclose at the end
1860
	 *
1861
	 * @dataProvider basicOperationProviderForLocks
1862
	 *
1863
	 * @param string $operation operation name on the view
1864
	 * @param array $operationArgs arguments for the operation
1865
	 * @param string $path path of the locked item to check
1866
	 */
1867
	public function testLockBasicOperationUnlocksAfterException(
1868
		$operation,
1869
		$operationArgs,
1870
		$path
1871
	) {
1872
		$view = new \OC\Files\View('/' . $this->user . '/files/');
1873
1874
		$storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
1875
			->setMethods([$operation])
1876
			->getMock();
1877
1878
		\OC\Files\Filesystem::mount($storage, array(), $this->user . '/');
1879
1880
		// work directly on disk because mkdir might be mocked
1881
		$realPath = $storage->getSourcePath('');
1882
		mkdir($realPath . '/files');
1883
		mkdir($realPath . '/files/dir');
1884
		file_put_contents($realPath . '/files/test.txt', 'blah');
1885
		$storage->getScanner()->scan('files');
1886
1887
		$storage->expects($this->once())
1888
			->method($operation)
1889
			->will($this->returnCallback(
1890
				function() {
1891
					throw new \Exception('Simulated exception');
1892
				}
1893
			));
1894
1895
		$thrown = false;
1896
		try {
1897
			call_user_func_array(array($view, $operation), $operationArgs);
1898
		} catch (\Exception $e) {
1899
			$thrown = true;
1900
			$this->assertEquals('Simulated exception', $e->getMessage());
1901
		}
1902
		$this->assertTrue($thrown, 'Exception was rethrown');
1903
		$this->assertNull($this->getFileLockType($view, $path), 'File got unlocked after exception');
1904
	}
1905
1906
	/**
1907
	 * Test locks for fopen with fclose at the end
1908
	 *
1909
	 * @dataProvider basicOperationProviderForLocks
1910
	 *
1911
	 * @param string $operation operation name on the view
1912
	 * @param array $operationArgs arguments for the operation
1913
	 * @param string $path path of the locked item to check
1914
	 * @param string $hookType hook type
1915
	 */
1916
	public function testLockBasicOperationUnlocksAfterCancelledHook(
1917
		$operation,
1918
		$operationArgs,
1919
		$path,
1920
		$hookType
1921
	) {
1922
		$view = new \OC\Files\View('/' . $this->user . '/files/');
1923
1924
		$storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
1925
			->setMethods([$operation])
1926
			->getMock();
1927
1928
		\OC\Files\Filesystem::mount($storage, array(), $this->user . '/');
1929
		$storage->mkdir('files');
1930
1931
		\OCP\Util::connectHook(
1932
			\OC\Files\Filesystem::CLASSNAME,
1933
			$hookType,
1934
			'\Test\HookHelper',
1935
			'cancellingCallback'
1936
		);
1937
1938
		call_user_func_array(array($view, $operation), $operationArgs);
1939
1940
		$this->assertNull($this->getFileLockType($view, $path), 'File got unlocked after exception');
1941
	}
1942
1943
	public function lockFileRenameOrCopyDataProvider() {
1944
		return [
1945
			['rename', ILockingProvider::LOCK_EXCLUSIVE],
1946
			['copy', ILockingProvider::LOCK_SHARED],
1947
		];
1948
	}
1949
1950
	/**
1951
	 * Test locks for rename or copy operation
1952
	 *
1953
	 * @dataProvider lockFileRenameOrCopyDataProvider
1954
	 *
1955
	 * @param string $operation operation to be done on the view
1956
	 * @param int $expectedLockTypeSourceDuring expected lock type on source file during
1957
	 * the operation
1958
	 */
1959
	public function testLockFileRename($operation, $expectedLockTypeSourceDuring) {
1960
		$view = new \OC\Files\View('/' . $this->user . '/files/');
1961
1962
		$storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
1963
			->setMethods([$operation])
1964
			->getMock();
1965
1966
		$sourcePath = 'original.txt';
1967
		$targetPath = 'target.txt';
1968
1969
		\OC\Files\Filesystem::mount($storage, array(), $this->user . '/');
1970
		$storage->mkdir('files');
1971
		$view->file_put_contents($sourcePath, 'meh');
1972
1973
		$storage->expects($this->once())
1974
			->method($operation)
1975
			->will($this->returnCallback(
1976 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...
1977
					$lockTypeSourceDuring = $this->getFileLockType($view, $sourcePath);
1978
					$lockTypeTargetDuring = $this->getFileLockType($view, $targetPath);
1979
1980
					return true;
1981
				}
1982
			));
1983
1984
		$this->connectMockHooks($operation, $view, $sourcePath, $lockTypeSourcePre, $lockTypeSourcePost);
1985
		$this->connectMockHooks($operation, $view, $targetPath, $lockTypeTargetPre, $lockTypeTargetPost);
1986
1987
		$this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked before operation');
1988
		$this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked before operation');
1989
1990
		$view->$operation($sourcePath, $targetPath);
1991
1992
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePre, 'Source file locked properly during pre-hook');
1993
		$this->assertEquals($expectedLockTypeSourceDuring, $lockTypeSourceDuring, 'Source file locked properly during operation');
1994
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePost, 'Source file locked properly during post-hook');
1995
1996
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPre, 'Target file locked properly during pre-hook');
1997
		$this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeTargetDuring, 'Target file locked properly during operation');
1998
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPost, 'Target file locked properly during post-hook');
1999
2000
		$this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked after operation');
2001
		$this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked after operation');
2002
	}
2003
2004
	/**
2005
	 * simulate a failed copy operation.
2006
	 * We expect that we catch the exception, free the lock and re-throw it.
2007
	 *
2008
	 * @expectedException \Exception
2009
	 */
2010
	public function testLockFileCopyException() {
2011
		$view = new \OC\Files\View('/' . $this->user . '/files/');
2012
2013
		$storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
2014
			->setMethods(['copy'])
2015
			->getMock();
2016
2017
		$sourcePath = 'original.txt';
2018
		$targetPath = 'target.txt';
2019
2020
		\OC\Files\Filesystem::mount($storage, array(), $this->user . '/');
2021
		$storage->mkdir('files');
2022
		$view->file_put_contents($sourcePath, 'meh');
2023
2024
		$storage->expects($this->once())
2025
			->method('copy')
2026
			->will($this->returnCallback(
2027
				function() {
2028
					throw new \Exception();
2029
				}
2030
			));
2031
2032
		$this->connectMockHooks('copy', $view, $sourcePath, $lockTypeSourcePre, $lockTypeSourcePost);
2033
		$this->connectMockHooks('copy', $view, $targetPath, $lockTypeTargetPre, $lockTypeTargetPost);
2034
2035
		$this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked before operation');
2036
		$this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked before operation');
2037
2038
		try {
2039
			$view->copy($sourcePath, $targetPath);
2040
		} catch (\Exception $e) {
2041
			$this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked after operation');
2042
			$this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked after operation');
2043
			throw $e;
2044
		}
2045
	}
2046
2047
	/**
2048
	 * Test rename operation: unlock first path when second path was locked
2049
	 */
2050
	public function testLockFileRenameUnlockOnException() {
2051
		$this->loginAsUser('test');
2052
2053
		$view = new \OC\Files\View('/' . $this->user . '/files/');
2054
2055
		$sourcePath = 'original.txt';
2056
		$targetPath = 'target.txt';
2057
		$view->file_put_contents($sourcePath, 'meh');
2058
2059
		// simulate that the target path is already locked
2060
		$view->lockFile($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
2061
2062
		$this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked before operation');
2063
		$this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $this->getFileLockType($view, $targetPath), 'Target file is locked before operation');
2064
2065
		$thrown = false;
2066
		try {
2067
			$view->rename($sourcePath, $targetPath);
2068
		} catch (\OCP\Lock\LockedException $e) {
2069
			$thrown = true;
2070
		}
2071
2072
		$this->assertTrue($thrown, 'LockedException thrown');
2073
2074
		$this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked after operation');
2075
		$this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $this->getFileLockType($view, $targetPath), 'Target file still locked after operation');
2076
2077
		$view->unlockFile($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
2078
	}
2079
2080
	public function lockFileRenameOrCopyCrossStorageDataProvider() {
2081
		return [
2082
			['rename', 'moveFromStorage', ILockingProvider::LOCK_EXCLUSIVE],
2083
			['copy', 'copyFromStorage', ILockingProvider::LOCK_SHARED],
2084
		];
2085
	}
2086
2087
	/**
2088
	 * Test locks for rename or copy operation cross-storage
2089
	 *
2090
	 * @dataProvider lockFileRenameOrCopyCrossStorageDataProvider
2091
	 *
2092
	 * @param string $viewOperation operation to be done on the view
2093
	 * @param string $storageOperation operation to be mocked on the storage
2094
	 * @param int $expectedLockTypeSourceDuring expected lock type on source file during
2095
	 * the operation
2096
	 */
2097
	public function testLockFileRenameCrossStorage($viewOperation, $storageOperation, $expectedLockTypeSourceDuring) {
2098
		$view = new \OC\Files\View('/' . $this->user . '/files/');
2099
2100
		$storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
2101
			->setMethods([$storageOperation])
2102
			->getMock();
2103
		$storage2 = $this->getMockBuilder('\OC\Files\Storage\Temporary')
2104
			->setMethods([$storageOperation])
2105
			->getMock();
2106
2107
		$sourcePath = 'original.txt';
2108
		$targetPath = 'substorage/target.txt';
2109
2110
		\OC\Files\Filesystem::mount($storage, array(), $this->user . '/');
2111
		\OC\Files\Filesystem::mount($storage2, array(), $this->user . '/files/substorage');
2112
		$storage->mkdir('files');
2113
		$view->file_put_contents($sourcePath, 'meh');
2114
2115
		$storage->expects($this->never())
2116
			->method($storageOperation);
2117
		$storage2->expects($this->once())
2118
			->method($storageOperation)
2119
			->will($this->returnCallback(
2120 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...
2121
					$lockTypeSourceDuring = $this->getFileLockType($view, $sourcePath);
2122
					$lockTypeTargetDuring = $this->getFileLockType($view, $targetPath);
2123
2124
					return true;
2125
				}
2126
			));
2127
2128
		$this->connectMockHooks($viewOperation, $view, $sourcePath, $lockTypeSourcePre, $lockTypeSourcePost);
2129
		$this->connectMockHooks($viewOperation, $view, $targetPath, $lockTypeTargetPre, $lockTypeTargetPost);
2130
2131
		$this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked before operation');
2132
		$this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked before operation');
2133
2134
		$view->$viewOperation($sourcePath, $targetPath);
2135
2136
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePre, 'Source file locked properly during pre-hook');
2137
		$this->assertEquals($expectedLockTypeSourceDuring, $lockTypeSourceDuring, 'Source file locked properly during operation');
2138
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePost, 'Source file locked properly during post-hook');
2139
2140
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPre, 'Target file locked properly during pre-hook');
2141
		$this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeTargetDuring, 'Target file locked properly during operation');
2142
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPost, 'Target file locked properly during post-hook');
2143
2144
		$this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked after operation');
2145
		$this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked after operation');
2146
	}
2147
2148
	/**
2149
	 * Test locks when moving a mount point
2150
	 */
2151
	public function testLockMoveMountPoint() {
2152
		$this->loginAsUser('test');
2153
2154
		list($mount) = $this->createTestMovableMountPoints([
2155
			$this->user . '/files/substorage',
2156
		]);
2157
2158
		$view = new \OC\Files\View('/' . $this->user . '/files/');
2159
		$view->mkdir('subdir');
2160
2161
		$sourcePath = 'substorage';
2162
		$targetPath = 'subdir/substorage_moved';
2163
2164
		$mount->expects($this->once())
2165
			->method('moveMount')
2166
			->will($this->returnCallback(
2167
				function($target) use ($mount, $view, $sourcePath, $targetPath, &$lockTypeSourceDuring, &$lockTypeTargetDuring, &$lockTypeSharedRootDuring){
2168
					$lockTypeSourceDuring = $this->getFileLockType($view, $sourcePath, true);
2169
					$lockTypeTargetDuring = $this->getFileLockType($view, $targetPath, true);
2170
2171
					$lockTypeSharedRootDuring = $this->getFileLockType($view, $sourcePath, false);
2172
2173
					$mount->setMountPoint($target);
2174
2175
					return true;
2176
				}
2177
			));
2178
2179
		$this->connectMockHooks('rename', $view, $sourcePath, $lockTypeSourcePre, $lockTypeSourcePost, true);
2180
		$this->connectMockHooks('rename', $view, $targetPath, $lockTypeTargetPre, $lockTypeTargetPost, true);
2181
		// in pre-hook, mount point is still on $sourcePath
2182
		$this->connectMockHooks('rename', $view, $sourcePath, $lockTypeSharedRootPre, $dummy, false);
2183
		// in post-hook, mount point is now on $targetPath
2184
		$this->connectMockHooks('rename', $view, $targetPath, $dummy, $lockTypeSharedRootPost, false);
2185
2186
		$this->assertNull($this->getFileLockType($view, $sourcePath, false), 'Shared storage root not locked before operation');
2187
		$this->assertNull($this->getFileLockType($view, $sourcePath, true), 'Source path not locked before operation');
2188
		$this->assertNull($this->getFileLockType($view, $targetPath, true), 'Target path not locked before operation');
2189
2190
		$view->rename($sourcePath, $targetPath);
2191
2192
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePre, 'Source path locked properly during pre-hook');
2193
		$this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeSourceDuring, 'Source path locked properly during operation');
2194
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePost, 'Source path locked properly during post-hook');
2195
2196
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPre, 'Target path locked properly during pre-hook');
2197
		$this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeTargetDuring, 'Target path locked properly during operation');
2198
		$this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPost, 'Target path locked properly during post-hook');
2199
2200
		$this->assertNull($lockTypeSharedRootPre, 'Shared storage root not locked during pre-hook');
2201
		$this->assertNull($lockTypeSharedRootDuring, 'Shared storage root not locked during move');
2202
		$this->assertNull($lockTypeSharedRootPost, 'Shared storage root not locked during post-hook');
2203
2204
		$this->assertNull($this->getFileLockType($view, $sourcePath, false), 'Shared storage root not locked after operation');
2205
		$this->assertNull($this->getFileLockType($view, $sourcePath, true), 'Source path not locked after operation');
2206
		$this->assertNull($this->getFileLockType($view, $targetPath, true), 'Target path not locked after operation');
2207
	}
2208
2209
	/**
2210
	 * Connect hook callbacks for hook type
2211
	 *
2212
	 * @param string $hookType hook type or null for none
2213
	 * @param \OC\Files\View $view view to check the lock on
2214
	 * @param string $path path for which to check the lock
2215
	 * @param int $lockTypePre variable to receive lock type that was active in the pre-hook
2216
	 * @param int $lockTypePost variable to receive lock type that was active in the post-hook
2217
	 * @param bool $onMountPoint true to check the mount point instead of the
2218
	 * mounted storage
2219
	 */
2220
	private function connectMockHooks($hookType, $view, $path, &$lockTypePre, &$lockTypePost, $onMountPoint = false) {
2221
		if ($hookType === null) {
2222
			return;
2223
		}
2224
2225
		$eventHandler = $this->getMockBuilder('\stdclass')
2226
			->setMethods(['preCallback', 'postCallback'])
2227
			->getMock();
2228
2229
		$eventHandler->expects($this->any())
2230
			->method('preCallback')
2231
			->will($this->returnCallback(
2232
				function() use ($view, $path, $onMountPoint, &$lockTypePre){
2233
					$lockTypePre = $this->getFileLockType($view, $path, $onMountPoint);
2234
				}
2235
			));
2236
		$eventHandler->expects($this->any())
2237
			->method('postCallback')
2238
			->will($this->returnCallback(
2239
				function() use ($view, $path, $onMountPoint, &$lockTypePost){
2240
					$lockTypePost = $this->getFileLockType($view, $path, $onMountPoint);
2241
				}
2242
			));
2243
2244
		if ($hookType !== null) {
2245
			\OCP\Util::connectHook(
2246
				\OC\Files\Filesystem::CLASSNAME,
2247
				$hookType,
2248
				$eventHandler,
2249
				'preCallback'
2250
			);
2251
			\OCP\Util::connectHook(
2252
				\OC\Files\Filesystem::CLASSNAME,
2253
				'post_' . $hookType,
2254
				$eventHandler,
2255
				'postCallback'
2256
			);
2257
		}
2258
	}
2259
2260
	/**
2261
	 * Returns the file lock type
2262
	 *
2263
	 * @param \OC\Files\View $view view
2264
	 * @param string $path path
2265
	 * @param bool $onMountPoint true to check the mount point instead of the
2266
	 * mounted storage
2267
	 *
2268
	 * @return int lock type or null if file was not locked
2269
	 */
2270
	private function getFileLockType(\OC\Files\View $view, $path, $onMountPoint = false) {
2271
		if ($this->isFileLocked($view, $path, ILockingProvider::LOCK_EXCLUSIVE, $onMountPoint)) {
2272
			return ILockingProvider::LOCK_EXCLUSIVE;
2273
		} else if ($this->isFileLocked($view, $path, ILockingProvider::LOCK_SHARED, $onMountPoint)) {
2274
			return ILockingProvider::LOCK_SHARED;
2275
		}
2276
		return null;
2277
	}
2278
}
2279