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