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