a400c8ba1ab0b0b18b375a705b54347a1328a0f4
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Unit / Resource / Driver / LocalDriverTest.php
1 <?php
2 namespace TYPO3\CMS\Core\Tests\Unit\Resource\Driver;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use org\bovigo\vfs\vfsStream;
18 use org\bovigo\vfs\vfsStreamWrapper;
19 use TYPO3\CMS\Core\Resource\Exception\InvalidFileNameException;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21 use TYPO3\TestingFramework\Core\FileStreamWrapper;
22
23 /**
24 * Testcase for the local storage driver class of the TYPO3 VFS
25 */
26 class LocalDriverTest extends \TYPO3\CMS\Core\Tests\Unit\Resource\BaseTestCase
27 {
28 /**
29 * Subject is not notice free, disable E_NOTICES
30 */
31 protected static $suppressNotices = true;
32
33 /**
34 * @var \TYPO3\CMS\Core\Resource\Driver\LocalDriver
35 */
36 protected $localDriver = null;
37
38 /**
39 * @var array A backup of registered singleton instances
40 */
41 protected $singletonInstances = [];
42
43 /**
44 * @var array
45 */
46 protected $testDirs = [];
47
48 /**
49 * @var string
50 */
51 protected $iso88591GreaterThan127 = '';
52
53 /**
54 * @var string
55 */
56 protected $utf8Latin1Supplement = '';
57
58 /**
59 * @var string
60 */
61 protected $utf8Latin1ExtendedA = '';
62
63 /**
64 * Tear down
65 */
66 protected function tearDown()
67 {
68 foreach ($this->testDirs as $dir) {
69 chmod($dir, 0777);
70 \TYPO3\CMS\Core\Utility\GeneralUtility::rmdir($dir, true);
71 }
72 parent::tearDown();
73 }
74
75 /**
76 * Creates a "real" directory for doing tests. This is necessary because some file system properties (e.g. permissions)
77 * cannot be reflected by vfsStream, and some methods (like touch()) don't work there either.
78 *
79 * Created directories are automatically destroyed during tearDown()
80 *
81 * @return string
82 */
83 protected function createRealTestdir()
84 {
85 $basedir = PATH_site . 'typo3temp/var/tests/' . $this->getUniqueId('fal-test-');
86 mkdir($basedir);
87 $this->testDirs[] = $basedir;
88 return $basedir;
89 }
90
91 /**
92 * Create a "real" directory together with a driver configured
93 * for this directory.
94 *
95 * @return array With path to base directory and driver
96 */
97 protected function prepareRealTestEnvironment()
98 {
99 $basedir = $this->createRealTestdir();
100 $subject = $this->createDriver([
101 'basePath' => $basedir
102 ]);
103 return [$basedir, $subject];
104 }
105
106 /**
107 * Creates a mocked driver object as test subject, optionally using a given mount object.
108 *
109 * IMPORTANT: Call this only after setting up the virtual file system (with the addTo* methods)!
110 *
111 * @param array $driverConfiguration
112 * @param array $mockedDriverMethods
113 * @return \TYPO3\CMS\Core\Resource\Driver\LocalDriver
114 */
115 protected function createDriver($driverConfiguration = [], $mockedDriverMethods = [])
116 {
117 // it's important to do that here, so vfsContents could have been set before
118 if (!isset($driverConfiguration['basePath'])) {
119 $this->initializeVfs();
120 $driverConfiguration['basePath'] = $this->getMountRootUrl();
121 }
122 /** @var \TYPO3\CMS\Core\Resource\Driver\LocalDriver $driver */
123 $mockedDriverMethods[] = 'isPathValid';
124 $driver = $this->getAccessibleMock(\TYPO3\CMS\Core\Resource\Driver\LocalDriver::class, $mockedDriverMethods, [$driverConfiguration]);
125 $driver->expects($this->any())
126 ->method('isPathValid')
127 ->will(
128 $this->returnValue(true)
129 );
130
131 $driver->setStorageUid(5);
132 $driver->processConfiguration();
133 $driver->initialize();
134 return $driver;
135 }
136
137 /**
138 * @test
139 */
140 public function calculatedBasePathRelativeIsSane()
141 {
142 $subject = $this->createDriver();
143
144 // This would cause problems if you fill "/fileadmin/" into the base path field of a sys_file_storage record and select "relative" as path type
145 $relativeDriverConfiguration = [
146 'pathType' => 'relative',
147 'basePath' => '/typo3temp/var/tests/',
148 ];
149 $basePath = $subject->_call('calculateBasePath', $relativeDriverConfiguration);
150
151 $this->assertNotContains('//', $basePath);
152 }
153
154 /**
155 * @test
156 */
157 public function calculatedBasePathAbsoluteIsSane()
158 {
159 $subject = $this->createDriver();
160
161 // This test checks if "/../" are properly filtered out (i.e. from "Base path" field of sys_file_storage)
162 $relativeDriverConfiguration = [
163 'basePath' => PATH_site . 'typo3temp/var/tests/../../../typo3temp/var/tests/',
164 ];
165 $basePath = $subject->_call('calculateBasePath', $relativeDriverConfiguration);
166
167 $this->assertNotContains('/../', $basePath);
168 }
169
170 /**
171 * @test
172 */
173 public function createFolderRecursiveSanitizesFilename()
174 {
175 /** @var \TYPO3\CMS\Core\Resource\Driver\LocalDriver|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $driver */
176 $driver = $this->createDriver([], ['sanitizeFilename']);
177 $driver->expects($this->exactly(2))
178 ->method('sanitizeFileName')
179 ->will(
180 $this->returnValue('sanitized')
181 );
182 $driver->createFolder('newFolder/andSubfolder', '/', true);
183 $this->assertFileExists($this->getUrlInMount('/sanitized/sanitized/'));
184 }
185
186 /**
187 * @test
188 */
189 public function determineBaseUrlUrlEncodesUriParts()
190 {
191 /** @var \TYPO3\CMS\Core\Resource\Driver\LocalDriver|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $driver */
192 $driver = $this->getAccessibleMock(\TYPO3\CMS\Core\Resource\Driver\LocalDriver::class, ['hasCapability'], [], '', false);
193 $driver->expects($this->once())
194 ->method('hasCapability')
195 ->with(\TYPO3\CMS\Core\Resource\ResourceStorage::CAPABILITY_PUBLIC)
196 ->will(
197 $this->returnValue(true)
198 );
199 $driver->_set('absoluteBasePath', PATH_site . 'un encö/ded %path/');
200 $driver->_call('determineBaseUrl');
201 $baseUri = $driver->_get('baseUri');
202 $this->assertEquals(rawurlencode('un encö') . '/' . rawurlencode('ded %path') . '/', $baseUri);
203 }
204
205 /**
206 * @test
207 */
208 public function getDefaultFolderReturnsFolderForUserUploadPath()
209 {
210 $subject = $this->createDriver();
211 $folderIdentifier = $subject->getDefaultFolder();
212 $this->assertEquals('/user_upload/', $folderIdentifier);
213 }
214
215 /**
216 * @test
217 */
218 public function defaultLevelFolderFolderIsCreatedIfItDoesntExist()
219 {
220 $subject = $this->createDriver();
221 $this->assertFileExists($this->getUrlInMount($subject->getDefaultFolder()));
222 }
223
224 /**
225 * @test
226 */
227 public function getFolderInFolderReturnsCorrectFolderObject()
228 {
229 $this->addToMount([
230 'someDir' => [
231 'someSubdir' => []
232 ]
233 ]);
234 $subject = $this->createDriver();
235 $folder = $subject->getFolderInFolder('someSubdir', '/someDir/');
236 $this->assertEquals('/someDir/someSubdir/', $folder);
237 }
238
239 /**
240 * @test
241 */
242 public function createFolderCreatesFolderOnDisk()
243 {
244 $this->addToMount(['some' => ['folder' => []]]);
245 $subject = $this->createDriver();
246 $subject->createFolder('path', '/some/folder/');
247 $this->assertFileExists($this->getUrlInMount('/some/folder/'));
248 $this->assertFileExists($this->getUrlInMount('/some/folder/path'));
249 }
250
251 /**
252 * @test
253 */
254 public function createFolderReturnsFolderObject()
255 {
256 $this->addToMount(['some' => ['folder' => []]]);
257 $subject = $this->createDriver();
258 $createdFolder = $subject->createFolder('path', '/some/folder/');
259 $this->assertEquals('/some/folder/path/', $createdFolder);
260 }
261
262 public static function createFolderSanitizesFolderNameBeforeCreationDataProvider()
263 {
264 return [
265 'folder name with NULL character' => [
266 'some' . chr(0) . 'Folder',
267 'some_Folder'
268 ],
269 'folder name with directory part' => [
270 '../someFolder',
271 '.._someFolder'
272 ]
273 ];
274 }
275
276 /**
277 * @test
278 * @dataProvider createFolderSanitizesFolderNameBeforeCreationDataProvider
279 */
280 public function createFolderSanitizesFolderNameBeforeCreation($newFolderName, $expectedFolderName)
281 {
282 $this->addToMount(['some' => ['folder' => []]]);
283 $subject = $this->createDriver();
284 $subject->createFolder($newFolderName, '/some/folder/');
285 $this->assertFileExists($this->getUrlInMount('/some/folder/' . $expectedFolderName));
286 }
287
288 /**
289 * @test
290 */
291 public function basePathIsNormalizedWithTrailingSlash()
292 {
293 $subject = $this->createDriver();
294 $this->assertEquals('/', substr($subject->_call('getAbsoluteBasePath'), -1));
295 }
296
297 /**
298 * @test
299 */
300 public function noSecondSlashIsAddedIfBasePathAlreadyHasTrailingSlash()
301 {
302 $subject = $this->createDriver();
303 $this->assertNotEquals('/', substr($subject->_call('getAbsoluteBasePath'), -2, 1));
304 }
305
306 public function getSpecificFileInformationDataProvider()
307 {
308 return [
309 'size' => [
310 'expectedValue' => filesize(__DIR__ . '/Fixtures/Dummy.html'),
311 'propertyName' => 'size'
312 ],
313 'atime' => [
314 'expectedValue' => 'WILL_BE_REPLACED_BY_VFS_TIME',
315 'propertyName' => 'atime'
316 ],
317 'mtime' => [
318 'expectedValue' => 'WILL_BE_REPLACED_BY_VFS_TIME',
319 'propertyName' => 'mtime'
320 ],
321 'ctime' => [
322 'expectedValue' => 'WILL_BE_REPLACED_BY_VFS_TIME',
323 'propertyName' => 'ctime'
324 ],
325 'name' => [
326 'expectedValue' => 'Dummy.html',
327 'propertyName' => 'name'
328 ],
329 'mimetype' => [
330 'expectedValue' => 'text/html',
331 'propertyName' => 'mimetype'
332 ],
333 'identifier' => [
334 'expectedValue' => '/Dummy.html',
335 'propertyName' => 'identifier'
336 ],
337 'storage' => [
338 'expectedValue' => 5,
339 'propertyName' => 'storage'
340 ],
341 'identifier_hash' => [
342 'expectedValue' => 'b11efa5d7c0556a65c6aa261343b9807cac993bc',
343 'propertyName' => 'identifier_hash'
344 ],
345 'folder_hash' => [
346 'expectedValue' => '42099b4af021e53fd8fd4e056c2568d7c2e3ffa8',
347 'propertyName' => 'folder_hash'
348 ]
349 ];
350 }
351
352 /**
353 * @test
354 * @dataProvider getSpecificFileInformationDataProvider
355 */
356 public function getSpecificFileInformationReturnsRequestedFileInformation($expectedValue, $property)
357 {
358 $root = vfsStream::setup();
359 $subFolder = vfsStream::newDirectory('fileadmin');
360 $root->addChild($subFolder);
361 // Load fixture files and folders from disk
362 $directory = vfsStream::copyFromFileSystem(__DIR__ . '/Fixtures/', $subFolder, 1024*1024);
363 if (in_array($property, ['mtime', 'ctime', 'atime'])) {
364 $expectedValue = $directory->getChild('Dummy.html')->filemtime();
365 }
366 FileStreamWrapper::init(PATH_site);
367 FileStreamWrapper::registerOverlayPath('fileadmin', 'vfs://root/fileadmin', false);
368
369 $subject = $this->createDriver(['basePath' => PATH_site . 'fileadmin']);
370 $this->assertSame(
371 $expectedValue,
372 $subject->getSpecificFileInformation(PATH_site . 'fileadmin/Dummy.html', '/', $property)
373 );
374
375 FileStreamWrapper::destroy();
376 }
377
378 /**
379 * @test
380 */
381 public function getAbsolutePathReturnsCorrectPath()
382 {
383 $this->addToMount([
384 'someFolder' => [
385 'file1.ext' => 'asdfg'
386 ]
387 ]);
388 $subject = $this->createDriver();
389 $path = $subject->_call('getAbsolutePath', '/someFolder/file1.ext');
390 $this->assertTrue(file_exists($path));
391 $this->assertEquals($this->getUrlInMount('/someFolder/file1.ext'), $path);
392 }
393
394 /**
395 * @test
396 */
397 public function addFileMovesFileToCorrectLocation()
398 {
399 $this->addToMount(['targetFolder' => []]);
400 $this->addToVfs([
401 'sourceFolder' => [
402 'file' => 'asdf'
403 ]
404 ]);
405 $subject = $this->createDriver(
406 [],
407 ['getMimeTypeOfFile']
408 );
409 $this->assertTrue(file_exists($this->getUrl('sourceFolder/file')));
410 $subject->addFile($this->getUrl('sourceFolder/file'), '/targetFolder/', 'file');
411 $this->assertTrue(file_exists($this->getUrlInMount('/targetFolder/file')));
412 }
413
414 /**
415 * @test
416 */
417 public function addFileUsesFilenameIfGiven()
418 {
419 $this->addToMount(['targetFolder' => []]);
420 $this->addToVfs([
421 'sourceFolder' => [
422 'file' => 'asdf'
423 ]
424 ]);
425 $subject = $this->createDriver(
426 [],
427 ['getMimeTypeOfFile']
428 );
429 $this->assertTrue(file_exists($this->getUrl('sourceFolder/file')));
430 $subject->addFile($this->getUrl('sourceFolder/file'), '/targetFolder/', 'targetFile');
431 $this->assertTrue(file_exists($this->getUrlInMount('/targetFolder/targetFile')));
432 }
433
434 /**
435 * @test
436 */
437 public function addFileFailsIfFileIsInDriverStorage()
438 {
439 $this->expectException(\InvalidArgumentException::class);
440 $this->expectExceptionCode(1314778269);
441 $this->addToMount([
442 'targetFolder' => [
443 'file' => 'asdf'
444 ]
445 ]);
446 $subject = $this->createDriver();
447 $subject->addFile($this->getUrlInMount('/targetFolder/file'), '/targetFolder/', 'file');
448 }
449
450 /**
451 * @test
452 */
453 public function addFileReturnsFileIdentifier()
454 {
455 $this->addToMount(['targetFolder' => []]);
456 $this->addToVfs([
457 'sourceFolder' => [
458 'file' => 'asdf'
459 ]
460 ]);
461 $subject = $this->createDriver(
462 [],
463 ['getMimeTypeOfFile']
464 );
465 $this->assertTrue(file_exists($this->getUrl('sourceFolder/file')));
466 $fileIdentifier = $subject->addFile($this->getUrl('sourceFolder/file'), '/targetFolder/', 'file');
467 $this->assertEquals('file', basename($fileIdentifier));
468 $this->assertEquals('/targetFolder/file', $fileIdentifier);
469 }
470
471 /**
472 * @test
473 */
474 public function existenceChecksWorkForFilesAndFolders()
475 {
476 $this->addToMount([
477 'file' => 'asdf',
478 'folder' => []
479 ]);
480 $subject = $this->createDriver();
481 // Using slashes at the beginning of paths because they will be stored in the DB this way.
482 $this->assertTrue($subject->fileExists('/file'));
483 $this->assertTrue($subject->folderExists('/folder/'));
484 $this->assertFalse($subject->fileExists('/nonexistingFile'));
485 $this->assertFalse($subject->folderExists('/nonexistingFolder/'));
486 }
487
488 /**
489 * @test
490 */
491 public function existenceChecksInFolderWorkForFilesAndFolders()
492 {
493 $this->addToMount([
494 'subfolder' => [
495 'file' => 'asdf',
496 'folder' => []
497 ]
498 ]);
499 $subject = $this->createDriver();
500 $this->assertTrue($subject->fileExistsInFolder('file', '/subfolder/'));
501 $this->assertTrue($subject->folderExistsInFolder('folder', '/subfolder/'));
502 $this->assertFalse($subject->fileExistsInFolder('nonexistingFile', '/subfolder/'));
503 $this->assertFalse($subject->folderExistsInFolder('nonexistingFolder', '/subfolder/'));
504 }
505
506 /**
507 * @test
508 */
509 public function getPublicUrlReturnsCorrectUriForConfiguredBaseUri()
510 {
511 $baseUri = 'http://example.org/foobar/' . $this->getUniqueId();
512 $this->addToMount([
513 'file.ext' => 'asdf',
514 'subfolder' => [
515 'file2.ext' => 'asdf'
516 ]
517 ]);
518 $subject = $this->createDriver([
519 'baseUri' => $baseUri
520 ]);
521 $this->assertEquals($baseUri . '/file.ext', $subject->getPublicUrl('/file.ext'));
522 $this->assertEquals($baseUri . '/subfolder/file2.ext', $subject->getPublicUrl('/subfolder/file2.ext'));
523 }
524
525 /**
526 * Data provider for getPublicUrlReturnsValidUrlContainingSpecialCharacters().
527 *
528 * @return array
529 */
530 public function getPublicUrlReturnsValidUrlContainingSpecialCharacters_dataProvider()
531 {
532 return [
533 ['/single file with some special chars äüö!.txt'],
534 ['/on subfolder/with special chars äüö!.ext'],
535 ['/who names a file like !"§$%&()=?*+~"#\'´`<>-.ext'],
536 ['no leading slash !"§$%&()=?*+~#\'"´`"<>-.txt']
537 ];
538 }
539
540 /**
541 * @test
542 * @dataProvider getPublicUrlReturnsValidUrlContainingSpecialCharacters_dataProvider
543 */
544 public function getPublicUrlReturnsValidUrlContainingSpecialCharacters($fileIdentifier)
545 {
546 $baseUri = 'http://example.org/foobar/' . $this->getUniqueId();
547 $subject = $this->createDriver([
548 'baseUri' => $baseUri
549 ]);
550 $publicUrl = $subject->getPublicUrl($fileIdentifier);
551 $this->assertTrue(GeneralUtility::isValidUrl($publicUrl), 'getPublicUrl did not return a valid URL:' . $publicUrl);
552 }
553
554 /**
555 * @test
556 */
557 public function fileContentsCanBeWrittenAndRead()
558 {
559 $fileContents = 'asdf';
560 $this->addToMount([
561 'file.ext' => $fileContents
562 ]);
563 $subject = $this->createDriver();
564 $this->assertEquals($fileContents, $subject->getFileContents('/file.ext'), 'File contents could not be read');
565 $newFileContents = 'asdfgh';
566 $subject->setFileContents('/file.ext', $newFileContents);
567 $this->assertEquals($newFileContents, $subject->getFileContents('/file.ext'), 'New file contents could not be read.');
568 }
569
570 /**
571 * @test
572 */
573 public function setFileContentsReturnsNumberOfBytesWrittenToFile()
574 {
575 $fileContents = 'asdf';
576 $this->addToMount([
577 'file.ext' => $fileContents
578 ]);
579 $subject = $this->createDriver();
580 $newFileContents = 'asdfgh';
581 $bytesWritten = $subject->setFileContents('/file.ext', $newFileContents);
582 $this->assertEquals(strlen($newFileContents), $bytesWritten);
583 }
584
585 /**
586 * @test
587 * @see http://phpmagazin.de/vfsStream-1.1.0-nutzt-PHP-5.4-M%C3%B6glichkeiten-064406.html
588 */
589 public function newFilesCanBeCreated()
590 {
591 $subject = $this->createDriver();
592 $subject->createFile('testfile.txt', '/');
593 $this->assertTrue($subject->fileExists('/testfile.txt'));
594 }
595
596 /**
597 * @test
598 * @see http://phpmagazin.de/vfsStream-1.1.0-nutzt-PHP-5.4-M%C3%B6glichkeiten-064406.html
599 */
600 public function createdFilesAreEmpty()
601 {
602 $subject = $this->createDriver();
603 $subject->createFile('testfile.txt', '/');
604 $this->assertTrue($subject->fileExists('/testfile.txt'));
605 $fileData = $subject->getFileContents('/testfile.txt');
606 $this->assertEquals(0, strlen($fileData));
607 }
608
609 /**
610 * @test
611 */
612 public function createFileFixesPermissionsOnCreatedFile()
613 {
614 if (TYPO3_OS === 'WIN') {
615 $this->markTestSkipped('createdFilesHaveCorrectRights() tests not available on Windows');
616 }
617
618 // No one will use this as his default file create mask so we hopefully don't get any false positives
619 $testpattern = '0646';
620 $GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'] = $testpattern;
621
622 $this->addToMount(
623 [
624 'someDir' => []
625 ]
626 );
627 /** @var $subject \TYPO3\CMS\Core\Resource\Driver\LocalDriver */
628 list($basedir, $subject) = $this->prepareRealTestEnvironment();
629 mkdir($basedir . '/someDir');
630 $subject->createFile('testfile.txt', '/someDir');
631 $this->assertEquals($testpattern, decoct(fileperms($basedir . '/someDir/testfile.txt') & 0777));
632 }
633
634 /**********************************
635 * File and directory listing
636 **********************************/
637 /**
638 * @test
639 */
640 public function getFileReturnsCorrectIdentifier()
641 {
642 $root = vfsStream::setup();
643 $subFolder = vfsStream::newDirectory('fileadmin');
644 $root->addChild($subFolder);
645 // Load fixture files and folders from disk
646 vfsStream::copyFromFileSystem(__DIR__ . '/Fixtures/', $subFolder, 1024*1024);
647 FileStreamWrapper::init(PATH_site);
648 FileStreamWrapper::registerOverlayPath('fileadmin/', 'vfs://root/fileadmin/', false);
649
650 $subject = $this->createDriver(['basePath' => PATH_site . 'fileadmin']);
651
652 $subdirFileInfo = $subject->getFileInfoByIdentifier('Dummy.html');
653 $this->assertEquals('/Dummy.html', $subdirFileInfo['identifier']);
654 $rootFileInfo = $subject->getFileInfoByIdentifier('LocalDriverFilenameFilter.php');
655 $this->assertEquals('/LocalDriverFilenameFilter.php', $rootFileInfo['identifier']);
656
657 FileStreamWrapper::destroy();
658 }
659
660 /**
661 * @test
662 */
663 public function getFileThrowsExceptionIfFileDoesNotExist()
664 {
665 $this->expectException(\InvalidArgumentException::class);
666 $this->expectExceptionCode(1314516809);
667 $subject = $this->createDriver();
668 $subject->getFileInfoByIdentifier('/some/file/at/a/random/path');
669 }
670
671 /**
672 * @test
673 */
674 public function getFilesInFolderReturnsEmptyArrayForEmptyDirectory()
675 {
676 $subject = $this->createDriver();
677 $fileList = $subject->getFilesInFolder('/');
678 $this->assertEmpty($fileList);
679 }
680
681 /**
682 * @test
683 */
684 public function getFileListReturnsAllFilesInDirectory()
685 {
686 $dirStructure = [
687 'aDir' => [],
688 'file1' => 'asdfg',
689 'file2' => 'fdsa'
690 ];
691 $this->addToMount($dirStructure);
692 $subject = $this->createDriver(
693 [],
694 // Mocked because finfo() can not deal with vfs streams and throws warnings
695 ['getMimeTypeOfFile']
696 );
697 $fileList = $subject->getFilesInFolder('/');
698 $this->assertEquals(['/file1', '/file2'], array_keys($fileList));
699 }
700
701 /**
702 * @test
703 */
704 public function getFileListReturnsAllFilesInSubdirectoryIfRecursiveParameterIsSet()
705 {
706 $dirStructure = [
707 'aDir' => [
708 'file3' => 'asdfgh',
709 'subdir' => [
710 'file4' => 'asklfjklasjkl'
711 ]
712 ],
713 'file1' => 'asdfg',
714 'file2' => 'fdsa'
715 ];
716 $this->addToMount($dirStructure);
717 $subject = $this->createDriver(
718 [],
719 // Mocked because finfo() can not deal with vfs streams and throws warnings
720 ['getMimeTypeOfFile']
721 );
722 $fileList = $subject->getFilesInFolder('/', 0, 0, true);
723 $this->assertEquals(['/file1', '/file2', '/aDir/file3', '/aDir/subdir/file4'], array_keys($fileList));
724 }
725
726 /**
727 * @test
728 */
729 public function getFileListFailsIfDirectoryDoesNotExist()
730 {
731 $this->expectException(\InvalidArgumentException::class);
732 $this->expectExceptionCode(1314349666);
733 $this->addToMount(['somefile' => '']);
734 $subject = $this->createDriver();
735 $subject->getFilesInFolder('somedir/');
736 }
737
738 /**
739 * @test
740 */
741 public function getFileInFolderCallsConfiguredCallbackFunctionWithGivenItemName()
742 {
743 $dirStructure = [
744 'file2' => 'fdsa'
745 ];
746 // register static callback to self
747 $callback = [
748 [
749 static::class,
750 'callbackStaticTestFunction'
751 ]
752 ];
753 $this->addToMount($dirStructure);
754 $subject = $this->createDriver();
755 // the callback function will throw an exception used to check if it was called with correct $itemName
756 $this->expectException(\InvalidArgumentException::class);
757 $this->expectExceptionCode(1336159604);
758 $subject->getFilesInFolder('/', 0, 0, false, $callback);
759 }
760
761 /**
762 * Static callback function used to test if the filter callbacks work
763 * As it is static we are using an exception to test if it is really called and works
764 *
765 * @static
766 * @param string $itemName
767 * @throws \InvalidArgumentException
768 * @see getFileListCallsConfiguredCallbackFunction
769 */
770 public static function callbackStaticTestFunction($itemName)
771 {
772 if ($itemName === 'file2') {
773 throw new \InvalidArgumentException('$itemName', 1336159604);
774 }
775 }
776
777 /**
778 * @test
779 */
780 public function getFileListFiltersItemsWithGivenFilterMethods()
781 {
782 $dirStructure = [
783 'fileA' => 'asdfg',
784 'fileB' => 'fdsa'
785 ];
786 $this->addToMount($dirStructure);
787 $subject = $this->createDriver(
788 [],
789 // Mocked because finfo() can not deal with vfs streams and throws warnings
790 ['getMimeTypeOfFile']
791 );
792 $filterCallbacks = [
793 [
794 \TYPO3\CMS\Core\Tests\Unit\Resource\Driver\Fixtures\LocalDriverFilenameFilter::class,
795 'filterFilename',
796 ],
797 ];
798 $fileList = $subject->getFilesInFolder('/', 0, 0, false, $filterCallbacks);
799 $this->assertNotContains('/fileA', array_keys($fileList));
800 }
801
802 /**
803 * @test
804 */
805 public function getFolderListReturnsAllDirectoriesInDirectory()
806 {
807 $dirStructure = [
808 'dir1' => [],
809 'dir2' => [],
810 'file' => 'asdfg'
811 ];
812 $this->addToMount($dirStructure);
813 $subject = $this->createDriver();
814 $fileList = $subject->getFoldersInFolder('/');
815 $this->assertEquals(['/dir1/', '/dir2/'], array_keys($fileList));
816 }
817
818 /**
819 * @test
820 */
821 public function getFolderListReturnsHiddenFoldersByDefault()
822 {
823 $dirStructure = [
824 '.someHiddenDir' => [],
825 'aDir' => [],
826 'file1' => ''
827 ];
828 $this->addToMount($dirStructure);
829 $subject = $this->createDriver();
830
831 $fileList = $subject->getFoldersInFolder('/');
832
833 $this->assertEquals(['/.someHiddenDir/', '/aDir/'], array_keys($fileList));
834 }
835
836 /**
837 * Checks if the folder names . and .. are ignored when listing subdirectories
838 *
839 * @test
840 */
841 public function getFolderListLeavesOutNavigationalEntries()
842 {
843 // we have to add .. and . manually, as these are not included in vfsStream directory listings (as opposed
844 // to normal filelistings)
845 $this->addToMount([
846 '..' => [],
847 '.' => []
848 ]);
849 $subject = $this->createDriver();
850 $fileList = $subject->getFoldersInFolder('/');
851 $this->assertEmpty($fileList);
852 }
853
854 /**
855 * @test
856 */
857 public function getFolderListFiltersItemsWithGivenFilterMethods()
858 {
859 $dirStructure = [
860 'folderA' => [],
861 'folderB' => []
862 ];
863 $this->addToMount($dirStructure);
864 $subject = $this->createDriver();
865 $filterCallbacks = [
866 [
867 \TYPO3\CMS\Core\Tests\Unit\Resource\Driver\Fixtures\LocalDriverFilenameFilter::class,
868 'filterFilename',
869 ],
870 ];
871 $folderList = $subject->getFoldersInFolder('/', 0, 0, $filterCallbacks);
872 $this->assertNotContains('folderA', array_keys($folderList));
873 }
874
875 /**
876 * @test
877 */
878 public function getFolderListFailsIfDirectoryDoesNotExist()
879 {
880 $this->expectException(\InvalidArgumentException::class);
881 $this->expectExceptionCode(1314349666);
882 $subject = $this->createDriver();
883 vfsStream::create([$this->basedir => ['somefile' => '']]);
884 $subject->getFoldersInFolder('somedir/');
885 }
886
887 /**
888 * @test
889 */
890 public function hashReturnsCorrectHashes()
891 {
892 $contents = '68b329da9893e34099c7d8ad5cb9c940';
893 $expectedMd5Hash = '8c67dbaf0ba22f2e7fbc26413b86051b';
894 $expectedSha1Hash = 'a60cd808ba7a0bcfa37fa7f3fb5998e1b8dbcd9d';
895 $this->addToMount(['hashFile' => $contents]);
896 $subject = $this->createDriver();
897 $this->assertEquals($expectedSha1Hash, $subject->hash('/hashFile', 'sha1'));
898 $this->assertEquals($expectedMd5Hash, $subject->hash('/hashFile', 'md5'));
899 }
900
901 /**
902 * @test
903 */
904 public function hashingWithUnsupportedAlgorithmFails()
905 {
906 $this->expectException(\InvalidArgumentException::class);
907 $this->expectExceptionCode(1304964032);
908 $subject = $this->createDriver();
909 $subject->hash('/hashFile', $this->getUniqueId());
910 }
911
912 /**
913 * @test
914 * @covers TYPO3\CMS\Core\Resource\Driver\LocalDriver::getFileForLocalProcessing
915 */
916 public function getFileForLocalProcessingCreatesCopyOfFileByDefault()
917 {
918 $fileContents = 'asdfgh';
919 $this->addToMount([
920 'someDir' => [
921 'someFile' => $fileContents
922 ]
923 ]);
924 $subject = $this->createDriver([], ['copyFileToTemporaryPath']);
925 $subject->expects($this->once())->method('copyFileToTemporaryPath');
926 $subject->getFileForLocalProcessing('/someDir/someFile');
927 }
928
929 /**
930 * @test
931 */
932 public function getFileForLocalProcessingReturnsOriginalFilepathForReadonlyAccess()
933 {
934 $fileContents = 'asdfgh';
935 $this->addToMount([
936 'someDir' => [
937 'someFile' => $fileContents
938 ]
939 ]);
940 $subject = $this->createDriver();
941 $filePath = $subject->getFileForLocalProcessing('/someDir/someFile', false);
942 $this->assertEquals($filePath, $this->getUrlInMount('someDir/someFile'));
943 }
944
945 /**
946 * @test
947 */
948 public function filesCanBeCopiedToATemporaryPath()
949 {
950 $fileContents = 'asdfgh';
951 $this->addToMount([
952 'someDir' => [
953 'someFile' => $fileContents
954 ]
955 ]);
956 $subject = $this->createDriver();
957 $filePath = GeneralUtility::fixWindowsFilePath($subject->_call('copyFileToTemporaryPath', '/someDir/someFile'));
958 $this->testFilesToDelete[] = $filePath;
959 $this->assertContains('/typo3temp/var/transient/', $filePath);
960 $this->assertEquals($fileContents, file_get_contents($filePath));
961 }
962
963 /**
964 * @test
965 */
966 public function permissionsAreCorrectlyRetrievedForAllowedFile()
967 {
968 /** @var $subject \TYPO3\CMS\Core\Resource\Driver\LocalDriver */
969 list($basedir, $subject) = $this->prepareRealTestEnvironment();
970 touch($basedir . '/someFile');
971 chmod($basedir . '/someFile', 448);
972 clearstatcache();
973 $this->assertEquals(['r' => true, 'w' => true], $subject->getPermissions('/someFile'));
974 }
975
976 /**
977 * @test
978 */
979 public function permissionsAreCorrectlyRetrievedForForbiddenFile()
980 {
981 if (function_exists('posix_getegid') && posix_getegid() === 0) {
982 $this->markTestSkipped('Test skipped if run on linux as root');
983 } elseif (TYPO3_OS === 'WIN') {
984 $this->markTestSkipped('Test skipped if run on Windows system');
985 }
986 /** @var $subject \TYPO3\CMS\Core\Resource\Driver\LocalDriver */
987 list($basedir, $subject) = $this->prepareRealTestEnvironment();
988 touch($basedir . '/someForbiddenFile');
989 chmod($basedir . '/someForbiddenFile', 0);
990 clearstatcache();
991 $this->assertEquals(['r' => false, 'w' => false], $subject->getPermissions('/someForbiddenFile'));
992 }
993
994 /**
995 * @test
996 */
997 public function permissionsAreCorrectlyRetrievedForAllowedFolder()
998 {
999 /** @var $subject \TYPO3\CMS\Core\Resource\Driver\LocalDriver */
1000 list($basedir, $subject) = $this->prepareRealTestEnvironment();
1001 mkdir($basedir . '/someFolder');
1002 chmod($basedir . '/someFolder', 448);
1003 clearstatcache();
1004 $this->assertEquals(['r' => true, 'w' => true], $subject->getPermissions('/someFolder'));
1005 }
1006
1007 /**
1008 * @test
1009 */
1010 public function permissionsAreCorrectlyRetrievedForForbiddenFolder()
1011 {
1012 if (function_exists('posix_getegid') && posix_getegid() === 0) {
1013 $this->markTestSkipped('Test skipped if run on linux as root');
1014 } elseif (TYPO3_OS === 'WIN') {
1015 $this->markTestSkipped('Test skipped if run on Windows system');
1016 }
1017 /** @var $subject \TYPO3\CMS\Core\Resource\Driver\LocalDriver */
1018 list($basedir, $subject) = $this->prepareRealTestEnvironment();
1019 mkdir($basedir . '/someForbiddenFolder');
1020 chmod($basedir . '/someForbiddenFolder', 0);
1021 clearstatcache();
1022 $result = $subject->getPermissions('/someForbiddenFolder');
1023 // Change permissions back to writable, so the sub-folder can be removed in tearDown
1024 chmod($basedir . '/someForbiddenFolder', 0777);
1025 $this->assertEquals(['r' => false, 'w' => false], $result);
1026 }
1027
1028 /**
1029 * Dataprovider for getFilePermissionsReturnsCorrectPermissionsForFilesNotOwnedByCurrentUser test
1030 *
1031 * @return array group, filemode and expected result
1032 */
1033 public function getFilePermissionsReturnsCorrectPermissionsForFilesNotOwnedByCurrentUser_dataProvider()
1034 {
1035 $data = [];
1036 // On some OS, the posix_* functions do not exits
1037 if (function_exists('posix_getgid')) {
1038 $data = [
1039 'current group, readable/writable' => [
1040 posix_getgid(),
1041 48,
1042 ['r' => true, 'w' => true]
1043 ],
1044 'current group, readable/not writable' => [
1045 posix_getgid(),
1046 32,
1047 ['r' => true, 'w' => false]
1048 ],
1049 'current group, not readable/not writable' => [
1050 posix_getgid(),
1051 0,
1052 ['r' => false, 'w' => false]
1053 ]
1054 ];
1055 }
1056 $data = array_merge_recursive($data, [
1057 'arbitrary group, readable/writable' => [
1058 vfsStream::GROUP_USER_1,
1059 6,
1060 ['r' => true, 'w' => true]
1061 ],
1062 'arbitrary group, readable/not writable' => [
1063 vfsStream::GROUP_USER_1,
1064 436,
1065 ['r' => true, 'w' => false]
1066 ],
1067 'arbitrary group, not readable/not writable' => [
1068 vfsStream::GROUP_USER_1,
1069 432,
1070 ['r' => false, 'w' => false]
1071 ]
1072 ]);
1073 return $data;
1074 }
1075
1076 /**
1077 * @test
1078 * @dataProvider getFilePermissionsReturnsCorrectPermissionsForFilesNotOwnedByCurrentUser_dataProvider
1079 */
1080 public function getFilePermissionsReturnsCorrectPermissionsForFilesNotOwnedByCurrentUser($group, $permissions, $expectedResult)
1081 {
1082 if (TYPO3_OS === 'WIN') {
1083 $this->markTestSkipped('Test skipped if run on Windows system');
1084 }
1085 $this->addToMount([
1086 'testfile' => 'asdfg'
1087 ]);
1088 $subject = $this->createDriver();
1089 /** @var $fileObject vfsStreamContent */
1090 $fileObject = vfsStreamWrapper::getRoot()->getChild($this->mountDir)->getChild('testfile');
1091 // just use an "arbitrary" user here - it is only important that
1092 $fileObject->chown(vfsStream::OWNER_USER_1);
1093 $fileObject->chgrp($group);
1094 $fileObject->chmod($permissions);
1095 $this->assertEquals($expectedResult, $subject->getPermissions('/testfile'));
1096 }
1097
1098 /**
1099 * @test
1100 */
1101 public function isWithinRecognizesFilesWithinFolderAndInOtherFolders()
1102 {
1103 $subject = $this->createDriver();
1104 $this->assertTrue($subject->isWithin('/someFolder/', '/someFolder/test.jpg'));
1105 $this->assertTrue($subject->isWithin('/someFolder/', '/someFolder/subFolder/test.jpg'));
1106 $this->assertFalse($subject->isWithin('/someFolder/', '/someFolderWithALongName/test.jpg'));
1107 }
1108
1109 /**
1110 * @test
1111 */
1112 public function isWithinAcceptsFileAndFolderObjectsAsContent()
1113 {
1114 $subject = $this->createDriver();
1115 $this->assertTrue($subject->isWithin('/someFolder/', '/someFolder/test.jpg'));
1116 $this->assertTrue($subject->isWithin('/someFolder/', '/someFolder/subfolder/'));
1117 }
1118
1119 /**********************************
1120 * Copy/move file
1121 **********************************/
1122
1123 /**
1124 * @test
1125 */
1126 public function filesCanBeCopiedWithinStorage()
1127 {
1128 $fileContents = $this->getUniqueId();
1129 $this->addToMount([
1130 'someFile' => $fileContents,
1131 'targetFolder' => []
1132 ]);
1133 $subject = $this->createDriver(
1134 [],
1135 ['getMimeTypeOfFile']
1136 );
1137 $subject->copyFileWithinStorage('/someFile', '/targetFolder/', 'someFile');
1138 $this->assertFileEquals($this->getUrlInMount('/someFile'), $this->getUrlInMount('/targetFolder/someFile'));
1139 }
1140
1141 /**
1142 * @test
1143 */
1144 public function filesCanBeMovedWithinStorage()
1145 {
1146 $fileContents = $this->getUniqueId();
1147 $this->addToMount([
1148 'targetFolder' => [],
1149 'someFile' => $fileContents
1150 ]);
1151 $subject = $this->createDriver();
1152 $newIdentifier = $subject->moveFileWithinStorage('/someFile', '/targetFolder/', 'file');
1153 $this->assertEquals($fileContents, file_get_contents($this->getUrlInMount('/targetFolder/file')));
1154 $this->assertFileNotExists($this->getUrlInMount('/someFile'));
1155 $this->assertEquals('/targetFolder/file', $newIdentifier);
1156 }
1157
1158 /**
1159 * @test
1160 */
1161 public function fileMetadataIsChangedAfterMovingFile()
1162 {
1163 $fileContents = $this->getUniqueId();
1164 $this->addToMount([
1165 'targetFolder' => [],
1166 'someFile' => $fileContents
1167 ]);
1168 $subject = $this->createDriver(
1169 [],
1170 // Mocked because finfo() can not deal with vfs streams and throws warnings
1171 ['getMimeTypeOfFile']
1172 );
1173 $newIdentifier = $subject->moveFileWithinStorage('/someFile', '/targetFolder/', 'file');
1174 $fileMetadata = $subject->getFileInfoByIdentifier($newIdentifier);
1175 $this->assertEquals($newIdentifier, $fileMetadata['identifier']);
1176 }
1177
1178 public function renamingFiles_dataProvider()
1179 {
1180 return [
1181 'file in subfolder' => [
1182 [
1183 'targetFolder' => ['file' => '']
1184 ],
1185 '/targetFolder/file',
1186 'newFile',
1187 '/targetFolder/newFile'
1188 ],
1189 'file in rootfolder' => [
1190 [
1191 'fileInRoot' => ''
1192 ],
1193 '/fileInRoot',
1194 'newFile',
1195 '/newFile'
1196 ]
1197 ];
1198 }
1199
1200 /**
1201 * @test
1202 * @dataProvider renamingFiles_dataProvider
1203 */
1204 public function renamingFilesChangesFilenameOnDisk(array $filesystemStructure, $oldFileIdentifier, $newFileName, $expectedNewIdentifier)
1205 {
1206 $this->addToMount($filesystemStructure);
1207 $subject = $this->createDriver();
1208 $newIdentifier = $subject->renameFile($oldFileIdentifier, $newFileName);
1209 $this->assertFalse($subject->fileExists($oldFileIdentifier));
1210 $this->assertTrue($subject->fileExists($newIdentifier));
1211 $this->assertEquals($expectedNewIdentifier, $newIdentifier);
1212 }
1213
1214 /**
1215 * @test
1216 */
1217 public function renamingFilesFailsIfTargetFileExists()
1218 {
1219 $this->expectException(\TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException::class);
1220 $this->expectExceptionCode(1320291063);
1221 $this->addToMount([
1222 'targetFolder' => ['file' => '', 'newFile' => '']
1223 ]);
1224 $subject = $this->createDriver();
1225 $subject->renameFile('/targetFolder/file', 'newFile');
1226 }
1227
1228 /**
1229 * We use this data provider for testing move methods because there are some issues with the
1230 *
1231 * @return array
1232 */
1233 public function renamingFolders_dataProvider()
1234 {
1235 return [
1236 'folder in root folder' => [
1237 [
1238 'someFolder' => []
1239 ],
1240 '/someFolder/',
1241 'newFolder',
1242 '/newFolder/'
1243 ],
1244 'file in subfolder' => [
1245 [
1246 'subfolder' => [
1247 'someFolder' => []
1248 ]
1249 ],
1250 '/subfolder/someFolder/',
1251 'newFolder',
1252 '/subfolder/newFolder/'
1253 ]
1254 ];
1255 }
1256
1257 /**
1258 * @test
1259 * @dataProvider renamingFolders_dataProvider
1260 */
1261 public function renamingFoldersChangesFolderNameOnDisk(array $filesystemStructure, $oldFolderIdentifier, $newFolderName, $expectedNewIdentifier)
1262 {
1263 $this->addToMount($filesystemStructure);
1264 $subject = $this->createDriver();
1265 $mapping = $subject->renameFolder($oldFolderIdentifier, $newFolderName);
1266 $this->assertFalse($subject->folderExists($oldFolderIdentifier));
1267 $this->assertTrue($subject->folderExists($expectedNewIdentifier));
1268 $this->assertEquals($expectedNewIdentifier, $mapping[$oldFolderIdentifier]);
1269 }
1270
1271 /**
1272 * @test
1273 */
1274 public function renameFolderReturnsCorrectMappingInformationForAllFiles()
1275 {
1276 $fileContents = 'asdfg';
1277 $this->addToMount([
1278 'sourceFolder' => [
1279 'subFolder' => ['file' => $fileContents],
1280 'file2' => 'asdfg'
1281 ]
1282 ]);
1283 $subject = $this->createDriver();
1284 $mappingInformation = $subject->renameFolder('/sourceFolder/', 'newFolder');
1285 $this->assertTrue(is_array($mappingInformation));
1286 $this->assertEquals('/newFolder/', $mappingInformation['/sourceFolder/']);
1287 $this->assertEquals('/newFolder/file2', $mappingInformation['/sourceFolder/file2']);
1288 $this->assertEquals('/newFolder/subFolder/file', $mappingInformation['/sourceFolder/subFolder/file']);
1289 $this->assertEquals('/newFolder/subFolder/', $mappingInformation['/sourceFolder/subFolder/']);
1290 }
1291
1292 /**
1293 * @test
1294 */
1295 public function renameFolderRevertsRenamingIfFilenameMapCannotBeCreated()
1296 {
1297 $this->expectException(\RuntimeException::class);
1298 $this->expectExceptionCode(1334160746);
1299 $this->addToMount([
1300 'sourceFolder' => [
1301 'file' => 'asdfg'
1302 ]
1303 ]);
1304 $subject = $this->createDriver([], ['createIdentifierMap']);
1305 $subject->expects($this->atLeastOnce())->method('createIdentifierMap')->will(
1306 $this->throwException(
1307 new \TYPO3\CMS\Core\Resource\Exception\FileOperationErrorException('testing', 1476045666)
1308 )
1309 );
1310 $subject->renameFolder('/sourceFolder/', 'newFolder');
1311 $this->assertFileExists($this->getUrlInMount('/sourceFolder/file'));
1312 }
1313
1314 /**
1315 * @test
1316 */
1317 public function isFolderEmptyReturnsTrueForEmptyFolder()
1318 {
1319 // This also prepares the next few tests, so add more info than required for this test
1320 $this->addToMount([
1321 'emptyFolder' => []
1322 ]);
1323 $subject = $this->createDriver();
1324 $this->assertTrue($subject->isFolderEmpty('/emptyFolder/'));
1325 return $subject;
1326 }
1327
1328 /**
1329 * @test
1330 */
1331 public function isFolderEmptyReturnsFalseIfFolderHasFile()
1332 {
1333 $this->addToMount([
1334 'folderWithFile' => [
1335 'someFile' => ''
1336 ]
1337 ]);
1338 $subject = $this->createDriver();
1339 $this->assertFalse($subject->isFolderEmpty('/folderWithFile/'));
1340 }
1341
1342 /**
1343 * @test
1344 */
1345 public function isFolderEmptyReturnsFalseIfFolderHasSubfolder()
1346 {
1347 $this->addToMount([
1348 'folderWithSubfolder' => [
1349 'someFolder' => []
1350 ]
1351 ]);
1352 $subject = $this->createDriver();
1353 $this->assertFalse($subject->isFolderEmpty('/folderWithSubfolder/'));
1354 }
1355
1356 /**********************************
1357 * Copy/move folder
1358 **********************************/
1359 /**
1360 * @test
1361 */
1362 public function foldersCanBeMovedWithinStorage()
1363 {
1364 $fileContents = $this->getUniqueId();
1365 $this->addToMount([
1366 'sourceFolder' => [
1367 'file' => $fileContents,
1368 ],
1369 'targetFolder' => [],
1370 ]);
1371 $subject = $this->createDriver();
1372 /** @var \TYPO3\CMS\Core\Resource\Driver\LocalDriver $subject */
1373 $subject->moveFolderWithinStorage('/sourceFolder/', '/targetFolder/', 'someFolder');
1374 $this->assertTrue(file_exists($this->getUrlInMount('/targetFolder/someFolder/')));
1375 $this->assertEquals($fileContents, file_get_contents($this->getUrlInMount('/targetFolder/someFolder/file')));
1376 $this->assertFileNotExists($this->getUrlInMount('/sourceFolder'));
1377 }
1378
1379 /**
1380 * @test
1381 */
1382 public function moveFolderWithinStorageReturnsCorrectMappingInformationForAllFiles()
1383 {
1384 $fileContents = 'asdfg';
1385 $this->addToMount([
1386 'targetFolder' => [],
1387 'sourceFolder' => [
1388 'subFolder' => ['file' => $fileContents],
1389 'file' => 'asdfg'
1390 ]
1391 ]);
1392 $subject = $this->createDriver();
1393 $mappingInformation = $subject->moveFolderWithinStorage('/sourceFolder/', '/targetFolder/', 'sourceFolder');
1394 $this->assertEquals('/targetFolder/sourceFolder/file', $mappingInformation['/sourceFolder/file']);
1395 $this->assertEquals('/targetFolder/sourceFolder/subFolder/file', $mappingInformation['/sourceFolder/subFolder/file']);
1396 $this->assertEquals('/targetFolder/sourceFolder/subFolder/', $mappingInformation['/sourceFolder/subFolder/']);
1397 }
1398
1399 /**
1400 * @test
1401 */
1402 public function folderCanBeRenamedWhenMoving()
1403 {
1404 $this->addToMount([
1405 'sourceFolder' => [
1406 'file' => $this->getUniqueId(),
1407 ],
1408 'targetFolder' => [],
1409 ]);
1410 $subject = $this->createDriver();
1411 $subject->moveFolderWithinStorage('/sourceFolder/', '/targetFolder/', 'newFolder');
1412 $this->assertTrue(file_exists($this->getUrlInMount('/targetFolder/newFolder/')));
1413 }
1414
1415 /**
1416 * @test
1417 */
1418 public function copyFolderWithinStorageCopiesSingleFileToNewFolderName()
1419 {
1420 $this->addToMount([
1421 'sourceFolder' => [
1422 'file' => $this->getUniqueId(),
1423 ],
1424 'targetFolder' => [],
1425 ]);
1426 $subject = $this->createDriver();
1427 $subject->copyFolderWithinStorage('/sourceFolder/', '/targetFolder/', 'newFolderName');
1428 $this->assertTrue(is_file($this->getUrlInMount('/targetFolder/newFolderName/file')));
1429 }
1430
1431 /**
1432 * @test
1433 */
1434 public function copyFolderWithinStorageCopiesSingleSubFolderToNewFolderName()
1435 {
1436 list($basePath, $subject) = $this->prepareRealTestEnvironment();
1437 GeneralUtility::mkdir_deep($basePath . '/sourceFolder/subFolder');
1438 GeneralUtility::mkdir_deep($basePath . '/targetFolder');
1439
1440 $subject->copyFolderWithinStorage('/sourceFolder/', '/targetFolder/', 'newFolderName');
1441 $this->assertTrue(is_dir($basePath . '/targetFolder/newFolderName/subFolder'));
1442 }
1443
1444 /**
1445 * @test
1446 */
1447 public function copyFolderWithinStorageCopiesFileInSingleSubFolderToNewFolderName()
1448 {
1449 list($basePath, $subject) = $this->prepareRealTestEnvironment();
1450 GeneralUtility::mkdir_deep($basePath . '/sourceFolder/subFolder');
1451 GeneralUtility::mkdir_deep($basePath . '/targetFolder');
1452 file_put_contents($basePath . '/sourceFolder/subFolder/file', $this->getUniqueId());
1453 GeneralUtility::fixPermissions($basePath . '/sourceFolder/subFolder/file');
1454
1455 $subject->copyFolderWithinStorage('/sourceFolder/', '/targetFolder/', 'newFolderName');
1456 $this->assertTrue(is_file($basePath . '/targetFolder/newFolderName/subFolder/file'));
1457 }
1458
1459 ///////////////////////
1460 // Tests concerning sanitizeFileName
1461 ///////////////////////
1462
1463 /**
1464 * Set up data for sanitizeFileName tests
1465 */
1466 public function setUpCharacterStrings()
1467 {
1468 // Generate string containing all characters for the iso8859-1 charset, charcode greater than 127
1469 $this->iso88591GreaterThan127 = '';
1470 for ($i = 0xA0; $i <= 0xFF; $i++) {
1471 $this->iso88591GreaterThan127 .= chr($i);
1472 }
1473
1474 // Generate string containing all characters for the utf-8 Latin-1 Supplement (U+0080 to U+00FF)
1475 // without U+0080 to U+009F: control characters
1476 // Based on http://www.utf8-chartable.de/unicode-utf8-table.pl
1477 $this->utf8Latin1Supplement = '';
1478 for ($i = 0xA0; $i <= 0xBF; $i++) {
1479 $this->utf8Latin1Supplement .= chr(0xC2) . chr($i);
1480 }
1481 for ($i = 0x80; $i <= 0xBF; $i++) {
1482 $this->utf8Latin1Supplement .= chr(0xC3) . chr($i);
1483 }
1484
1485 // Generate string containing all characters for the utf-8 Latin-1 Extended-A (U+0100 to U+017F)
1486 $this->utf8Latin1ExtendedA = '';
1487 for ($i = 0x80; $i <= 0xBF; $i++) {
1488 $this->utf8Latin1ExtendedA .= chr(0xC4) . chr($i);
1489 }
1490 for ($i = 0x80; $i <= 0xBF; $i++) {
1491 $this->utf8Latin1ExtendedA .= chr(0xC5) . chr($i);
1492 }
1493 }
1494
1495 /**
1496 * Data provider for sanitizeFileNameUTF8FilesystemDataProvider
1497 *
1498 * Every array splits into:
1499 * - String value fileName
1500 * - String value charset (none = '', utf-8, latin1, etc.)
1501 * - Expected result (cleaned fileName)
1502 *
1503 * @return array
1504 */
1505 public function sanitizeFileNameUTF8FilesystemDataProvider()
1506 {
1507 $this->setUpCharacterStrings();
1508 return [
1509 // Characters ordered by ASCII table
1510 'allowed characters utf-8 (ASCII part)' => [
1511 '-.0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz',
1512 '-.0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz'
1513 ],
1514 // Characters ordered by ASCII table (except for space-character, because space-character ist trimmed)
1515 'replace special characters with _ (not allowed characters) utf-8 (ASCII part)' => [
1516 '! "#$%&\'()*+,/:;<=>?[\\]^`{|}~',
1517 '_____________________________'
1518 ],
1519 'utf-8 (Latin-1 Supplement)' => [
1520 $this->utf8Latin1Supplement,
1521 '________________________________ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
1522 ],
1523 'trim leading and tailing spaces utf-8' => [
1524 ' test.txt ',
1525 'test.txt'
1526 ],
1527 'remove tailing dot' => [
1528 'test.txt.',
1529 'test.txt'
1530 ],
1531 ];
1532 }
1533
1534 /**
1535 * @test
1536 * @dataProvider sanitizeFileNameUTF8FilesystemDataProvider
1537 */
1538 public function sanitizeFileNameUTF8Filesystem($fileName, $expectedResult)
1539 {
1540 $GLOBALS['TYPO3_CONF_VARS']['SYS']['UTF8filesystem'] = 1;
1541 $this->assertEquals(
1542 $expectedResult,
1543 $this->createDriver()->sanitizeFileName($fileName)
1544 );
1545 }
1546
1547 /**
1548 * Data provider for sanitizeFileNameNonUTF8Filesystem
1549 *
1550 * Every array splits into:
1551 * - String value fileName
1552 * - String value charset (none = '', utf-8, latin1, etc.)
1553 * - Expected result (cleaned fileName)
1554 *
1555 * @return array
1556 */
1557 public function sanitizeFileNameNonUTF8FilesystemDataProvider()
1558 {
1559 $this->setUpCharacterStrings();
1560 return [
1561 // Characters ordered by ASCII table
1562 'allowed characters iso-8859-1' => [
1563 '-.0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz',
1564 'iso-8859-1',
1565 '-.0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz'
1566 ],
1567 // Characters ordered by ASCII table
1568 'allowed characters utf-8' => [
1569 '-.0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz',
1570 'utf-8',
1571 '-.0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz'
1572 ],
1573 // Characters ordered by ASCII table (except for space-character, because space-character ist trimmed)
1574 'replace special characters with _ (not allowed characters) iso-8859-1' => [
1575 '! "#$%&\'()*+,/:;<=>?[\\]^`{|}~',
1576 'iso-8859-1',
1577 '_____________________________'
1578 ],
1579 // Characters ordered by ASCII table (except for space-character, because space-character ist trimmed)
1580 'replace special characters with _ (not allowed characters) utf-8' => [
1581 '! "#$%&\'()*+,/:;<=>?[\\]^`{|}~',
1582 'utf-8',
1583 '_____________________________'
1584 ],
1585 'iso-8859-1 (code > 127)' => [
1586 // http://de.wikipedia.org/wiki/ISO_8859-1
1587 // chr(0xA0) = NBSP (no-break space) => gets trimmed
1588 $this->iso88591GreaterThan127,
1589 'iso-8859-1',
1590 '_centpound_yen____c_a_____R_____-23_u___1o__1_41_23_4_AAAAAEAAAECEEEEIIIIDNOOOOOExOEUUUUEYTHssaaaaaeaaaeceeeeiiiidnoooooe_oeuuuueythy'
1591 ],
1592 'utf-8 (Latin-1 Supplement)' => [
1593 // chr(0xC2) . chr(0x0A) = NBSP (no-break space) => gets trimmed
1594 $this->utf8Latin1Supplement,
1595 'utf-8',
1596 '_centpound__yen______c_a_______R_______-23__u_____1o__1_41_23_4_AAAAAEAAAECEEEEIIIIDNOOOOOExOEUUUUEYTHssaaaaaeaaaeceeeeiiiidnoooooe_oeuuuueythy'
1597 ],
1598 'utf-8 (Latin-1 Extended A)' => [
1599 $this->utf8Latin1ExtendedA,
1600 'utf-8',
1601 'AaAaAaCcCcCcCcDdDdEeEeEeEeEeGgGgGgGgHhHhIiIiIiIiIiIJijJjKk__LlLlLlL_l_LlNnNnNn_n____OOooOoOoOEoeRrRrRrSsSsSsSsTtTtTtUuUuUuUuUuUuWwYyYZzZzZzs'
1602 ],
1603 'trim leading and tailing spaces iso-8859-1' => [
1604 ' test.txt ',
1605 'iso-8859-1',
1606 'test.txt'
1607 ],
1608 'trim leading and tailing spaces utf-8' => [
1609 ' test.txt ',
1610 'utf-8',
1611 'test.txt'
1612 ],
1613 'remove tailing dot iso-8859-1' => [
1614 'test.txt.',
1615 'iso-8859-1',
1616 'test.txt'
1617 ],
1618 'remove tailing dot utf-8' => [
1619 'test.txt.',
1620 'utf-8',
1621 'test.txt'
1622 ],
1623 ];
1624 }
1625
1626 /**
1627 * @test
1628 * @dataProvider sanitizeFileNameNonUTF8FilesystemDataProvider
1629 */
1630 public function sanitizeFileNameNonUTF8Filesystem($fileName, $charset, $expectedResult)
1631 {
1632 $GLOBALS['TYPO3_CONF_VARS']['SYS']['UTF8filesystem'] = 0;
1633 $this->assertEquals(
1634 $expectedResult,
1635 $this->createDriver()->sanitizeFileName($fileName, $charset)
1636 );
1637 }
1638
1639 /**
1640 * @test
1641 */
1642 public function sanitizeFileNameThrowsExceptionOnInvalidFileName()
1643 {
1644 $this->expectException(InvalidFileNameException::class);
1645 $this->expectExceptionCode(1320288991);
1646
1647 $GLOBALS['TYPO3_CONF_VARS']['SYS']['UTF8filesystem'] = 1;
1648 $this->createDriver()->sanitizeFileName('');
1649 }
1650
1651 /**
1652 * @test
1653 */
1654 public function applyFilterMethodsToDirectoryItemCallsFilterMethodIfClosure()
1655 {
1656 $this->expectException(\Exception::class);
1657 $this->expectExceptionCode(1463073434);
1658 $closure = function () {
1659 throw new \Exception('I was called!', 1463073434);
1660 };
1661
1662 $filterMethods = [
1663 $closure,
1664 ];
1665
1666 $this->createDriver()->_call('applyFilterMethodsToDirectoryItem', $filterMethods, '', '', '');
1667 }
1668
1669 /**
1670 * @test
1671 */
1672 public function applyFilterMethodsToDirectoryItemCallsFilterMethodIfName()
1673 {
1674 $dummyObject = $this
1675 ->getMockBuilder('\TYPO3\CMS\Core\Resource\Driver\LocalDriver')
1676 ->setMethods(['dummy'])
1677 ->disableOriginalConstructor()
1678 ->getMock();
1679 $method = [
1680 $dummyObject,
1681 'dummy',
1682 ];
1683 $dummyObject->expects($this->once())->method('dummy');
1684 $filterMethods = [
1685 $method,
1686 ];
1687 $this->createDriver()->_call('applyFilterMethodsToDirectoryItem', $filterMethods, '', '', '');
1688 }
1689 }