[TASK] typo3/testing-framework now flushes runtime caches
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Unit / Utility / GeneralUtilityTest.php
1 <?php
2 namespace TYPO3\CMS\Core\Tests\Unit\Utility;
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\vfsStreamDirectory;
19 use org\bovigo\vfs\vfsStreamWrapper;
20 use Prophecy\Argument;
21 use Psr\Http\Message\ResponseInterface;
22 use Psr\Http\Message\StreamInterface;
23 use Psr\Log\LoggerInterface;
24 use TYPO3\CMS\Core\Cache\CacheManager;
25 use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
26 use TYPO3\CMS\Core\Core\Environment;
27 use TYPO3\CMS\Core\Http\RequestFactory;
28 use TYPO3\CMS\Core\Package\Package;
29 use TYPO3\CMS\Core\Package\PackageManager;
30 use TYPO3\CMS\Core\Tests\Unit\Utility\AccessibleProxies\ExtensionManagementUtilityAccessibleProxy;
31 use TYPO3\CMS\Core\Tests\Unit\Utility\Fixtures\GeneralUtilityFilesystemFixture;
32 use TYPO3\CMS\Core\Tests\Unit\Utility\Fixtures\GeneralUtilityFixture;
33 use TYPO3\CMS\Core\Tests\Unit\Utility\Fixtures\GeneralUtilityMakeInstanceInjectLoggerFixture;
34 use TYPO3\CMS\Core\Tests\Unit\Utility\Fixtures\OriginalClassFixture;
35 use TYPO3\CMS\Core\Tests\Unit\Utility\Fixtures\OtherReplacementClassFixture;
36 use TYPO3\CMS\Core\Tests\Unit\Utility\Fixtures\ReplacementClassFixture;
37 use TYPO3\CMS\Core\Tests\Unit\Utility\Fixtures\TwoParametersConstructorFixture;
38 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
39 use TYPO3\CMS\Core\Utility\GeneralUtility;
40 use TYPO3\TestingFramework\Core\FileStreamWrapper;
41 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
42
43 /**
44 * Test case
45 */
46 class GeneralUtilityTest extends UnitTestCase
47 {
48 const NO_FIX_PERMISSIONS_ON_WINDOWS = 'fixPermissions() not available on Windows (method does nothing)';
49
50 /**
51 * @var bool Reset singletons created by subject
52 */
53 protected $resetSingletonInstances = true;
54
55 /**
56 * @var \TYPO3\CMS\Core\Package\PackageManager
57 */
58 protected $backupPackageManager;
59
60 /**
61 * Set up
62 */
63 protected function setUp()
64 {
65 GeneralUtilityFixture::$isAllowedHostHeaderValueCallCount = 0;
66 GeneralUtilityFixture::setAllowHostHeaderValue(false);
67 $GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] = GeneralUtility::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL;
68 $this->backupPackageManager = ExtensionManagementUtilityAccessibleProxy::getPackageManager();
69 }
70
71 /**
72 * Tear down
73 */
74 protected function tearDown()
75 {
76 ExtensionManagementUtilityAccessibleProxy::setPackageManager($this->backupPackageManager);
77 parent::tearDown();
78 }
79
80 /**
81 * Helper method to test for an existing internet connection.
82 * Some tests are skipped if there is no working uplink.
83 *
84 * @return bool $isConnected
85 */
86 public function isConnected()
87 {
88 $isConnected = false;
89 $connected = @fsockopen('typo3.org', 80);
90 if ($connected) {
91 $isConnected = true;
92 fclose($connected);
93 }
94 return $isConnected;
95 }
96
97 /**
98 * Helper method to create a random directory in the virtual file system
99 * and return the path.
100 *
101 * @param string $prefix
102 * @return string
103 */
104 protected function getVirtualTestDir($prefix = 'root_')
105 {
106 $root = vfsStream::setup();
107 $path = $root->url() . '/typo3temp/var/tests/' . $this->getUniqueId($prefix);
108 GeneralUtility::mkdir_deep($path);
109 return $path;
110 }
111
112 ///////////////////////////
113 // Tests concerning _GP
114 ///////////////////////////
115 /**
116 * @test
117 * @dataProvider gpDataProvider
118 */
119 public function canRetrieveValueWithGP($key, $get, $post, $expected)
120 {
121 $_GET = $get;
122 $_POST = $post;
123 $this->assertSame($expected, GeneralUtility::_GP($key));
124 }
125
126 /**
127 * Data provider for canRetrieveValueWithGP.
128 * All test values also check whether slashes are stripped properly.
129 *
130 * @return array
131 */
132 public function gpDataProvider()
133 {
134 return [
135 'No key parameter' => [null, [], [], null],
136 'Key not found' => ['cake', [], [], null],
137 'Value only in GET' => ['cake', ['cake' => 'li\\e'], [], 'li\\e'],
138 'Value only in POST' => ['cake', [], ['cake' => 'l\\ie'], 'l\\ie'],
139 'Value from POST preferred over GET' => ['cake', ['cake' => 'is a'], ['cake' => '\\lie'], '\\lie'],
140 'Value can be an array' => [
141 'cake',
142 ['cake' => ['is a' => 'l\\ie']],
143 [],
144 ['is a' => 'l\\ie']
145 ]
146 ];
147 }
148
149 ///////////////////////////
150 // Tests concerning _GPmerged
151 ///////////////////////////
152 /**
153 * @test
154 * @dataProvider gpMergedDataProvider
155 */
156 public function gpMergedWillMergeArraysFromGetAndPost($get, $post, $expected)
157 {
158 $_POST = $post;
159 $_GET = $get;
160 $this->assertEquals($expected, GeneralUtility::_GPmerged('cake'));
161 }
162
163 /**
164 * Data provider for gpMergedWillMergeArraysFromGetAndPost
165 *
166 * @return array
167 */
168 public function gpMergedDataProvider()
169 {
170 $fullDataArray = ['cake' => ['a' => 'is a', 'b' => 'lie']];
171 $postPartData = ['cake' => ['b' => 'lie']];
172 $getPartData = ['cake' => ['a' => 'is a']];
173 $getPartDataModified = ['cake' => ['a' => 'is not a']];
174 return [
175 'Key doesn\' exist' => [['foo'], ['bar'], []],
176 'No POST data' => [$fullDataArray, [], $fullDataArray['cake']],
177 'No GET data' => [[], $fullDataArray, $fullDataArray['cake']],
178 'POST and GET are merged' => [$getPartData, $postPartData, $fullDataArray['cake']],
179 'POST is preferred over GET' => [$getPartDataModified, $fullDataArray, $fullDataArray['cake']]
180 ];
181 }
182
183 ///////////////////////////////
184 // Tests concerning _GET / _POST
185 ///////////////////////////////
186 /**
187 * Data provider for canRetrieveGlobalInputsThroughGet
188 * and canRetrieveGlobalInputsThroughPost
189 *
190 * @return array
191 */
192 public function getAndPostDataProvider()
193 {
194 return [
195 'Requested input data doesn\'t exist' => ['cake', [], null],
196 'No key will return entire input data' => [null, ['cake' => 'l\\ie'], ['cake' => 'l\\ie']],
197 'Can retrieve specific input' => ['cake', ['cake' => 'l\\ie', 'foo'], 'l\\ie'],
198 'Can retrieve nested input data' => ['cake', ['cake' => ['is a' => 'l\\ie']], ['is a' => 'l\\ie']]
199 ];
200 }
201
202 /**
203 * @test
204 * @dataProvider getAndPostDataProvider
205 */
206 public function canRetrieveGlobalInputsThroughGet($key, $get, $expected)
207 {
208 $_GET = $get;
209 $this->assertSame($expected, GeneralUtility::_GET($key));
210 }
211
212 /**
213 * @test
214 * @dataProvider getAndPostDataProvider
215 */
216 public function canRetrieveGlobalInputsThroughPost($key, $post, $expected)
217 {
218 $_POST = $post;
219 $this->assertSame($expected, GeneralUtility::_POST($key));
220 }
221
222 ///////////////////////////////
223 // Tests concerning _GETset
224 ///////////////////////////////
225 /**
226 * @test
227 * @dataProvider getSetDataProvider
228 */
229 public function canSetNewGetInputValues($input, $key, $expected, $getPreset = [])
230 {
231 $_GET = $getPreset;
232 GeneralUtility::_GETset($input, $key);
233 $this->assertSame($expected, $_GET);
234 }
235
236 /**
237 * Data provider for canSetNewGetInputValues
238 *
239 * @return array
240 */
241 public function getSetDataProvider()
242 {
243 return [
244 'No input data used without target key' => [null, null, []],
245 'No input data used with target key' => ['', 'cake', ['cake' => '']],
246 'No target key used with string input data' => ['data', null, []],
247 'No target key used with array input data' => [['cake' => 'lie'], null, ['cake' => 'lie']],
248 'Target key and string input data' => ['lie', 'cake', ['cake' => 'lie']],
249 'Replace existing GET data' => ['lie', 'cake', ['cake' => 'lie'], ['cake' => 'is a lie']],
250 'Target key pointing to sublevels and string input data' => ['lie', 'cake|is', ['cake' => ['is' => 'lie']]],
251 'Target key pointing to sublevels and array input data' => [['a' => 'lie'], 'cake|is', ['cake' => ['is' => ['a' => 'lie']]]]
252 ];
253 }
254
255 ///////////////////////////
256 // Tests concerning cmpIPv4
257 ///////////////////////////
258 /**
259 * Data provider for cmpIPv4ReturnsTrueForMatchingAddress
260 *
261 * @return array Data sets
262 */
263 public static function cmpIPv4DataProviderMatching()
264 {
265 return [
266 'host with full IP address' => ['127.0.0.1', '127.0.0.1'],
267 'host with two wildcards at the end' => ['127.0.0.1', '127.0.*.*'],
268 'host with wildcard at third octet' => ['127.0.0.1', '127.0.*.1'],
269 'host with wildcard at second octet' => ['127.0.0.1', '127.*.0.1'],
270 '/8 subnet' => ['127.0.0.1', '127.1.1.1/8'],
271 '/32 subnet (match only name)' => ['127.0.0.1', '127.0.0.1/32'],
272 '/30 subnet' => ['10.10.3.1', '10.10.3.3/30'],
273 'host with wildcard in list with IPv4/IPv6 addresses' => ['192.168.1.1', '127.0.0.1, 1234:5678::/126, 192.168.*'],
274 'host in list with IPv4/IPv6 addresses' => ['192.168.1.1', '::1, 1234:5678::/126, 192.168.1.1'],
275 ];
276 }
277
278 /**
279 * @test
280 * @dataProvider cmpIPv4DataProviderMatching
281 */
282 public function cmpIPv4ReturnsTrueForMatchingAddress($ip, $list)
283 {
284 $this->assertTrue(GeneralUtility::cmpIPv4($ip, $list));
285 }
286
287 /**
288 * Data provider for cmpIPv4ReturnsFalseForNotMatchingAddress
289 *
290 * @return array Data sets
291 */
292 public static function cmpIPv4DataProviderNotMatching()
293 {
294 return [
295 'single host' => ['127.0.0.1', '127.0.0.2'],
296 'single host with wildcard' => ['127.0.0.1', '127.*.1.1'],
297 'single host with /32 subnet mask' => ['127.0.0.1', '127.0.0.2/32'],
298 '/31 subnet' => ['127.0.0.1', '127.0.0.2/31'],
299 'list with IPv4/IPv6 addresses' => ['127.0.0.1', '10.0.2.3, 192.168.1.1, ::1'],
300 'list with only IPv6 addresses' => ['10.20.30.40', '::1, 1234:5678::/127']
301 ];
302 }
303
304 /**
305 * @test
306 * @dataProvider cmpIPv4DataProviderNotMatching
307 */
308 public function cmpIPv4ReturnsFalseForNotMatchingAddress($ip, $list)
309 {
310 $this->assertFalse(GeneralUtility::cmpIPv4($ip, $list));
311 }
312
313 ///////////////////////////
314 // Tests concerning cmpIPv6
315 ///////////////////////////
316 /**
317 * Data provider for cmpIPv6ReturnsTrueForMatchingAddress
318 *
319 * @return array Data sets
320 */
321 public static function cmpIPv6DataProviderMatching()
322 {
323 return [
324 'empty address' => ['::', '::'],
325 'empty with netmask in list' => ['::', '::/0'],
326 'empty with netmask 0 and host-bits set in list' => ['::', '::123/0'],
327 'localhost' => ['::1', '::1'],
328 'localhost with leading zero blocks' => ['::1', '0:0::1'],
329 'host with submask /128' => ['::1', '0:0::1/128'],
330 '/16 subnet' => ['1234::1', '1234:5678::/16'],
331 '/126 subnet' => ['1234:5678::3', '1234:5678::/126'],
332 '/126 subnet with host-bits in list set' => ['1234:5678::3', '1234:5678::2/126'],
333 'list with IPv4/IPv6 addresses' => ['1234:5678::3', '::1, 127.0.0.1, 1234:5678::/126, 192.168.1.1']
334 ];
335 }
336
337 /**
338 * @test
339 * @dataProvider cmpIPv6DataProviderMatching
340 */
341 public function cmpIPv6ReturnsTrueForMatchingAddress($ip, $list)
342 {
343 $this->assertTrue(GeneralUtility::cmpIPv6($ip, $list));
344 }
345
346 /**
347 * Data provider for cmpIPv6ReturnsFalseForNotMatchingAddress
348 *
349 * @return array Data sets
350 */
351 public static function cmpIPv6DataProviderNotMatching()
352 {
353 return [
354 'empty against localhost' => ['::', '::1'],
355 'empty against localhost with /128 netmask' => ['::', '::1/128'],
356 'localhost against different host' => ['::1', '::2'],
357 'localhost against host with prior bits set' => ['::1', '::1:1'],
358 'host against different /17 subnet' => ['1234::1', '1234:f678::/17'],
359 'host against different /127 subnet' => ['1234:5678::3', '1234:5678::/127'],
360 'host against IPv4 address list' => ['1234:5678::3', '127.0.0.1, 192.168.1.1'],
361 'host against mixed list with IPv6 host in different subnet' => ['1234:5678::3', '::1, 1234:5678::/127']
362 ];
363 }
364
365 /**
366 * @test
367 * @dataProvider cmpIPv6DataProviderNotMatching
368 */
369 public function cmpIPv6ReturnsFalseForNotMatchingAddress($ip, $list)
370 {
371 $this->assertFalse(GeneralUtility::cmpIPv6($ip, $list));
372 }
373
374 ///////////////////////////////
375 // Tests concerning IPv6Hex2Bin
376 ///////////////////////////////
377 /**
378 * Data provider for IPv6Hex2BinCorrect
379 *
380 * @return array Data sets
381 */
382 public static function IPv6Hex2BinDataProviderCorrect()
383 {
384 return [
385 'empty 1' => ['::', str_pad('', 16, "\x00")],
386 'empty 2, already normalized' => ['0000:0000:0000:0000:0000:0000:0000:0000', str_pad('', 16, "\x00")],
387 'already normalized' => ['0102:0304:0000:0000:0000:0000:0506:0078', "\x01\x02\x03\x04" . str_pad('', 8, "\x00") . "\x05\x06\x00\x78"],
388 'expansion in middle 1' => ['1::2', "\x00\x01" . str_pad('', 12, "\x00") . "\x00\x02"],
389 'expansion in middle 2' => ['beef::fefa', "\xbe\xef" . str_pad('', 12, "\x00") . "\xfe\xfa"],
390 ];
391 }
392
393 /**
394 * @test
395 * @dataProvider IPv6Hex2BinDataProviderCorrect
396 */
397 public function IPv6Hex2BinCorrectlyConvertsAddresses($hex, $binary)
398 {
399 $this->assertTrue(GeneralUtility::IPv6Hex2Bin($hex) === $binary);
400 }
401
402 ///////////////////////////////
403 // Tests concerning IPv6Bin2Hex
404 ///////////////////////////////
405 /**
406 * Data provider for IPv6Bin2HexCorrect
407 *
408 * @return array Data sets
409 */
410 public static function IPv6Bin2HexDataProviderCorrect()
411 {
412 return [
413 'empty' => [str_pad('', 16, "\x00"), '::'],
414 'non-empty front' => ["\x01" . str_pad('', 15, "\x00"), '100::'],
415 'non-empty back' => [str_pad('', 15, "\x00") . "\x01", '::1'],
416 'normalized' => ["\x01\x02\x03\x04" . str_pad('', 8, "\x00") . "\x05\x06\x00\x78", '102:304::506:78'],
417 'expansion in middle 1' => ["\x00\x01" . str_pad('', 12, "\x00") . "\x00\x02", '1::2'],
418 'expansion in middle 2' => ["\xbe\xef" . str_pad('', 12, "\x00") . "\xfe\xfa", 'beef::fefa'],
419 ];
420 }
421
422 /**
423 * @test
424 * @dataProvider IPv6Bin2HexDataProviderCorrect
425 */
426 public function IPv6Bin2HexCorrectlyConvertsAddresses($binary, $hex)
427 {
428 $this->assertEquals(GeneralUtility::IPv6Bin2Hex($binary), $hex);
429 }
430
431 ////////////////////////////////////////////////
432 // Tests concerning normalizeIPv6 / compressIPv6
433 ////////////////////////////////////////////////
434 /**
435 * Data provider for normalizeIPv6ReturnsCorrectlyNormalizedFormat
436 *
437 * @return array Data sets
438 */
439 public static function normalizeCompressIPv6DataProviderCorrect()
440 {
441 return [
442 'empty' => ['::', '0000:0000:0000:0000:0000:0000:0000:0000'],
443 'localhost' => ['::1', '0000:0000:0000:0000:0000:0000:0000:0001'],
444 'expansion in middle 1' => ['1::2', '0001:0000:0000:0000:0000:0000:0000:0002'],
445 'expansion in middle 2' => ['1:2::3', '0001:0002:0000:0000:0000:0000:0000:0003'],
446 'expansion in middle 3' => ['1::2:3', '0001:0000:0000:0000:0000:0000:0002:0003'],
447 'expansion in middle 4' => ['1:2::3:4:5', '0001:0002:0000:0000:0000:0003:0004:0005']
448 ];
449 }
450
451 /**
452 * @test
453 * @dataProvider normalizeCompressIPv6DataProviderCorrect
454 */
455 public function normalizeIPv6CorrectlyNormalizesAddresses($compressed, $normalized)
456 {
457 $this->assertEquals($normalized, GeneralUtility::normalizeIPv6($compressed));
458 }
459
460 /**
461 * @test
462 * @dataProvider normalizeCompressIPv6DataProviderCorrect
463 */
464 public function compressIPv6CorrectlyCompressesAdresses($compressed, $normalized)
465 {
466 $this->assertEquals($compressed, GeneralUtility::compressIPv6($normalized));
467 }
468
469 /**
470 * @test
471 */
472 public function compressIPv6CorrectlyCompressesAdressWithSomeAddressOnRightSide()
473 {
474 if (strtolower(PHP_OS) === 'darwin') {
475 $this->markTestSkipped('This test does not work on OSX / Darwin OS.');
476 }
477 $this->assertEquals('::f0f', GeneralUtility::compressIPv6('0000:0000:0000:0000:0000:0000:0000:0f0f'));
478 }
479
480 ///////////////////////////////
481 // Tests concerning validIP
482 ///////////////////////////////
483 /**
484 * Data provider for checkValidIpReturnsTrueForValidIp
485 *
486 * @return array Data sets
487 */
488 public static function validIpDataProvider()
489 {
490 return [
491 '0.0.0.0' => ['0.0.0.0'],
492 'private IPv4 class C' => ['192.168.0.1'],
493 'private IPv4 class A' => ['10.0.13.1'],
494 'private IPv6' => ['fe80::daa2:5eff:fe8b:7dfb']
495 ];
496 }
497
498 /**
499 * @test
500 * @dataProvider validIpDataProvider
501 */
502 public function validIpReturnsTrueForValidIp($ip)
503 {
504 $this->assertTrue(GeneralUtility::validIP($ip));
505 }
506
507 /**
508 * Data provider for checkValidIpReturnsFalseForInvalidIp
509 *
510 * @return array Data sets
511 */
512 public static function invalidIpDataProvider()
513 {
514 return [
515 'null' => [null],
516 'zero' => [0],
517 'string' => ['test'],
518 'string empty' => [''],
519 'string NULL' => ['NULL'],
520 'out of bounds IPv4' => ['300.300.300.300'],
521 'dotted decimal notation with only two dots' => ['127.0.1']
522 ];
523 }
524
525 /**
526 * @test
527 * @dataProvider invalidIpDataProvider
528 */
529 public function validIpReturnsFalseForInvalidIp($ip)
530 {
531 $this->assertFalse(GeneralUtility::validIP($ip));
532 }
533
534 ///////////////////////////////
535 // Tests concerning cmpFQDN
536 ///////////////////////////////
537 /**
538 * Data provider for cmpFqdnReturnsTrue
539 *
540 * @return array Data sets
541 */
542 public static function cmpFqdnValidDataProvider()
543 {
544 return [
545 'localhost should usually resolve, IPv4' => ['127.0.0.1', '*'],
546 'localhost should usually resolve, IPv6' => ['::1', '*'],
547 // other testcases with resolving not possible since it would
548 // require a working IPv4/IPv6-connectivity
549 'aaa.bbb.ccc.ddd.eee, full' => ['aaa.bbb.ccc.ddd.eee', 'aaa.bbb.ccc.ddd.eee'],
550 'aaa.bbb.ccc.ddd.eee, wildcard first' => ['aaa.bbb.ccc.ddd.eee', '*.ccc.ddd.eee'],
551 'aaa.bbb.ccc.ddd.eee, wildcard last' => ['aaa.bbb.ccc.ddd.eee', 'aaa.bbb.ccc.*'],
552 'aaa.bbb.ccc.ddd.eee, wildcard middle' => ['aaa.bbb.ccc.ddd.eee', 'aaa.*.eee'],
553 'list-matches, 1' => ['aaa.bbb.ccc.ddd.eee', 'xxx, yyy, zzz, aaa.*.eee'],
554 'list-matches, 2' => ['aaa.bbb.ccc.ddd.eee', '127:0:0:1,,aaa.*.eee,::1']
555 ];
556 }
557
558 /**
559 * @test
560 * @dataProvider cmpFqdnValidDataProvider
561 */
562 public function cmpFqdnReturnsTrue($baseHost, $list)
563 {
564 $this->assertTrue(GeneralUtility::cmpFQDN($baseHost, $list));
565 }
566
567 /**
568 * Data provider for cmpFqdnReturnsFalse
569 *
570 * @return array Data sets
571 */
572 public static function cmpFqdnInvalidDataProvider()
573 {
574 return [
575 'num-parts of hostname to check can only be less or equal than hostname, 1' => ['aaa.bbb.ccc.ddd.eee', 'aaa.bbb.ccc.ddd.eee.fff'],
576 'num-parts of hostname to check can only be less or equal than hostname, 2' => ['aaa.bbb.ccc.ddd.eee', 'aaa.*.bbb.ccc.ddd.eee']
577 ];
578 }
579
580 /**
581 * @test
582 * @dataProvider cmpFqdnInvalidDataProvider
583 */
584 public function cmpFqdnReturnsFalse($baseHost, $list)
585 {
586 $this->assertFalse(GeneralUtility::cmpFQDN($baseHost, $list));
587 }
588
589 ///////////////////////////////
590 // Tests concerning inList
591 ///////////////////////////////
592 /**
593 * @test
594 * @param string $haystack
595 * @dataProvider inListForItemContainedReturnsTrueDataProvider
596 */
597 public function inListForItemContainedReturnsTrue($haystack)
598 {
599 $this->assertTrue(GeneralUtility::inList($haystack, 'findme'));
600 }
601
602 /**
603 * Data provider for inListForItemContainedReturnsTrue.
604 *
605 * @return array
606 */
607 public function inListForItemContainedReturnsTrueDataProvider()
608 {
609 return [
610 'Element as second element of four items' => ['one,findme,three,four'],
611 'Element at beginning of list' => ['findme,one,two'],
612 'Element at end of list' => ['one,two,findme'],
613 'One item list' => ['findme']
614 ];
615 }
616
617 /**
618 * @test
619 * @param string $haystack
620 * @dataProvider inListForItemNotContainedReturnsFalseDataProvider
621 */
622 public function inListForItemNotContainedReturnsFalse($haystack)
623 {
624 $this->assertFalse(GeneralUtility::inList($haystack, 'findme'));
625 }
626
627 /**
628 * Data provider for inListForItemNotContainedReturnsFalse.
629 *
630 * @return array
631 */
632 public function inListForItemNotContainedReturnsFalseDataProvider()
633 {
634 return [
635 'Four item list' => ['one,two,three,four'],
636 'One item list' => ['one'],
637 'Empty list' => ['']
638 ];
639 }
640
641 ///////////////////////////////
642 // Tests concerning rmFromList
643 ///////////////////////////////
644 /**
645 * @test
646 * @param string $initialList
647 * @param string $listWithElementRemoved
648 * @dataProvider rmFromListRemovesElementsFromCommaSeparatedListDataProvider
649 */
650 public function rmFromListRemovesElementsFromCommaSeparatedList($initialList, $listWithElementRemoved)
651 {
652 $this->assertSame($listWithElementRemoved, GeneralUtility::rmFromList('removeme', $initialList));
653 }
654
655 /**
656 * Data provider for rmFromListRemovesElementsFromCommaSeparatedList
657 *
658 * @return array
659 */
660 public function rmFromListRemovesElementsFromCommaSeparatedListDataProvider()
661 {
662 return [
663 'Element as second element of three' => ['one,removeme,two', 'one,two'],
664 'Element at beginning of list' => ['removeme,one,two', 'one,two'],
665 'Element at end of list' => ['one,two,removeme', 'one,two'],
666 'One item list' => ['removeme', ''],
667 'Element not contained in list' => ['one,two,three', 'one,two,three'],
668 'Empty element survives' => ['one,,three,,removeme', 'one,,three,'],
669 'Empty element survives at start' => [',removeme,three,removeme', ',three'],
670 'Empty element survives at end' => ['removeme,three,removeme,', 'three,'],
671 'Empty list' => ['', ''],
672 'List contains removeme multiple times' => ['removeme,notme,removeme,removeme', 'notme'],
673 'List contains removeme multiple times nothing else' => ['removeme,removeme,removeme', ''],
674 'List contains removeme multiple times nothing else 2x' => ['removeme,removeme', ''],
675 'List contains removeme multiple times nothing else 3x' => ['removeme,removeme,removeme', ''],
676 'List contains removeme multiple times nothing else 4x' => ['removeme,removeme,removeme,removeme', ''],
677 'List contains removeme multiple times nothing else 5x' => ['removeme,removeme,removeme,removeme,removeme', ''],
678 ];
679 }
680
681 ///////////////////////////////
682 // Tests concerning expandList
683 ///////////////////////////////
684 /**
685 * @test
686 * @param string $list
687 * @param string $expectation
688 * @dataProvider expandListExpandsIntegerRangesDataProvider
689 */
690 public function expandListExpandsIntegerRanges($list, $expectation)
691 {
692 $this->assertSame($expectation, GeneralUtility::expandList($list));
693 }
694
695 /**
696 * Data provider for expandListExpandsIntegerRangesDataProvider
697 *
698 * @return array
699 */
700 public function expandListExpandsIntegerRangesDataProvider()
701 {
702 return [
703 'Expand for the same number' => ['1,2-2,7', '1,2,7'],
704 'Small range expand with parameters reversed ignores reversed items' => ['1,5-3,7', '1,7'],
705 'Small range expand' => ['1,3-5,7', '1,3,4,5,7'],
706 'Expand at beginning' => ['3-5,1,7', '3,4,5,1,7'],
707 'Expand at end' => ['1,7,3-5', '1,7,3,4,5'],
708 'Multiple small range expands' => ['1,3-5,7-10,12', '1,3,4,5,7,8,9,10,12'],
709 'One item list' => ['1-5', '1,2,3,4,5'],
710 'Nothing to expand' => ['1,2,3,4', '1,2,3,4'],
711 'Empty list' => ['', '']
712 ];
713 }
714
715 /**
716 * @test
717 */
718 public function expandListExpandsForTwoThousandElementsExpandsOnlyToThousandElementsMaximum()
719 {
720 $list = GeneralUtility::expandList('1-2000');
721 $this->assertSame(1000, count(explode(',', $list)));
722 }
723
724 ///////////////////////////////
725 // Tests concerning uniqueList
726 ///////////////////////////////
727 /**
728 * @test
729 * @param string $initialList
730 * @param string $unifiedList
731 * @dataProvider uniqueListUnifiesCommaSeparatedListDataProvider
732 */
733 public function uniqueListUnifiesCommaSeparatedList($initialList, $unifiedList)
734 {
735 $this->assertSame($unifiedList, GeneralUtility::uniqueList($initialList));
736 }
737
738 /**
739 * Data provider for uniqueListUnifiesCommaSeparatedList
740 *
741 * @return array
742 */
743 public function uniqueListUnifiesCommaSeparatedListDataProvider()
744 {
745 return [
746 'List without duplicates' => ['one,two,three', 'one,two,three'],
747 'List with two consecutive duplicates' => ['one,two,two,three,three', 'one,two,three'],
748 'List with non-consecutive duplicates' => ['one,two,three,two,three', 'one,two,three'],
749 'One item list' => ['one', 'one'],
750 'Empty list' => ['', '']
751 ];
752 }
753
754 ///////////////////////////////
755 // Tests concerning isFirstPartOfStr
756 ///////////////////////////////
757 /**
758 * Data provider for isFirstPartOfStrReturnsTrueForMatchingFirstParts
759 *
760 * @return array
761 */
762 public function isFirstPartOfStrReturnsTrueForMatchingFirstPartDataProvider()
763 {
764 return [
765 'match first part of string' => ['hello world', 'hello'],
766 'match whole string' => ['hello', 'hello'],
767 'integer is part of string with same number' => ['24', 24],
768 'string is part of integer with same number' => [24, '24'],
769 'integer is part of string starting with same number' => ['24 beer please', 24]
770 ];
771 }
772
773 /**
774 * @test
775 * @dataProvider isFirstPartOfStrReturnsTrueForMatchingFirstPartDataProvider
776 */
777 public function isFirstPartOfStrReturnsTrueForMatchingFirstPart($string, $part)
778 {
779 $this->assertTrue(GeneralUtility::isFirstPartOfStr($string, $part));
780 }
781
782 /**
783 * Data provider for checkIsFirstPartOfStrReturnsFalseForNotMatchingFirstParts
784 *
785 * @return array
786 */
787 public function isFirstPartOfStrReturnsFalseForNotMatchingFirstPartDataProvider()
788 {
789 return [
790 'no string match' => ['hello', 'bye'],
791 'no case sensitive string match' => ['hello world', 'Hello'],
792 'array is not part of string' => ['string', []],
793 'string is not part of array' => [[], 'string'],
794 'NULL is not part of string' => ['string', null],
795 'string is not part of NULL' => [null, 'string'],
796 'NULL is not part of array' => [[], null],
797 'array is not part of NULL' => [null, []],
798 'empty string is not part of empty string' => ['', ''],
799 'NULL is not part of empty string' => ['', null],
800 'false is not part of empty string' => ['', false],
801 'empty string is not part of NULL' => [null, ''],
802 'empty string is not part of false' => [false, ''],
803 'empty string is not part of zero integer' => [0, ''],
804 'zero integer is not part of NULL' => [null, 0],
805 'zero integer is not part of empty string' => ['', 0]
806 ];
807 }
808
809 /**
810 * @test
811 * @dataProvider isFirstPartOfStrReturnsFalseForNotMatchingFirstPartDataProvider
812 */
813 public function isFirstPartOfStrReturnsFalseForNotMatchingFirstPart($string, $part)
814 {
815 $this->assertFalse(GeneralUtility::isFirstPartOfStr($string, $part));
816 }
817
818 ///////////////////////////////
819 // Tests concerning formatSize
820 ///////////////////////////////
821 /**
822 * @test
823 * @dataProvider formatSizeDataProvider
824 */
825 public function formatSizeTranslatesBytesToHigherOrderRepresentation($size, $labels, $base, $expected)
826 {
827 $this->assertEquals($expected, GeneralUtility::formatSize($size, $labels, $base));
828 }
829
830 /**
831 * Data provider for formatSizeTranslatesBytesToHigherOrderRepresentation
832 *
833 * @return array
834 */
835 public function formatSizeDataProvider()
836 {
837 return [
838 'IEC Bytes stay bytes (min)' => [1, '', 0, '1 '],
839 'IEC Bytes stay bytes (max)' => [921, '', 0, '921 '],
840 'IEC Kilobytes are used (min)' => [922, '', 0, '0.90 Ki'],
841 'IEC Kilobytes are used (max)' => [943718, '', 0, '922 Ki'],
842 'IEC Megabytes are used (min)' => [943719, '', 0, '0.90 Mi'],
843 'IEC Megabytes are used (max)' => [966367641, '', 0, '922 Mi'],
844 'IEC Gigabytes are used (min)' => [966367642, '', 0, '0.90 Gi'],
845 'IEC Gigabytes are used (max)' => [989560464998, '', 0, '922 Gi'],
846 'IEC Decimal is omitted for large kilobytes' => [31080, '', 0, '30 Ki'],
847 'IEC Decimal is omitted for large megabytes' => [31458000, '', 0, '30 Mi'],
848 'IEC Decimal is omitted for large gigabytes' => [32212254720, '', 0, '30 Gi'],
849 'SI Bytes stay bytes (min)' => [1, 'si', 0, '1 '],
850 'SI Bytes stay bytes (max)' => [899, 'si', 0, '899 '],
851 'SI Kilobytes are used (min)' => [901, 'si', 0, '0.90 k'],
852 'SI Kilobytes are used (max)' => [900000, 'si', 0, '900 k'],
853 'SI Megabytes are used (min)' => [900001, 'si', 0, '0.90 M'],
854 'SI Megabytes are used (max)' => [900000000, 'si', 0, '900 M'],
855 'SI Gigabytes are used (min)' => [900000001, 'si', 0, '0.90 G'],
856 'SI Gigabytes are used (max)' => [900000000000, 'si', 0, '900 G'],
857 'SI Decimal is omitted for large kilobytes' => [30000, 'si', 0, '30 k'],
858 'SI Decimal is omitted for large megabytes' => [30000000, 'si', 0, '30 M'],
859 'SI Decimal is omitted for large gigabytes' => [30000000000, 'si', 0, '30 G'],
860 'Label for bytes can be exchanged (binary unit)' => [1, ' Foo|||', 0, '1 Foo'],
861 'Label for kilobytes can be exchanged (binary unit)' => [1024, '| Foo||', 0, '1.00 Foo'],
862 'Label for megabyes can be exchanged (binary unit)' => [1048576, '|| Foo|', 0, '1.00 Foo'],
863 'Label for gigabytes can be exchanged (binary unit)' => [1073741824, '||| Foo', 0, '1.00 Foo'],
864 'Label for bytes can be exchanged (decimal unit)' => [1, ' Foo|||', 1000, '1 Foo'],
865 'Label for kilobytes can be exchanged (decimal unit)' => [1000, '| Foo||', 1000, '1.00 Foo'],
866 'Label for megabyes can be exchanged (decimal unit)' => [1000000, '|| Foo|', 1000, '1.00 Foo'],
867 'Label for gigabytes can be exchanged (decimal unit)' => [1000000000, '||| Foo', 1000, '1.00 Foo'],
868 'IEC Base is ignored' => [1024, 'iec', 1000, '1.00 Ki'],
869 'SI Base is ignored' => [1000, 'si', 1024, '1.00 k'],
870 'Use binary base for unexpected base' => [2048, '| Bar||', 512, '2.00 Bar']
871 ];
872 }
873
874 ///////////////////////////////
875 // Tests concerning splitCalc
876 ///////////////////////////////
877 /**
878 * Data provider for splitCalc
879 *
880 * @return array expected values, arithmetic expression
881 */
882 public function splitCalcDataProvider()
883 {
884 return [
885 'empty string returns empty array' => [
886 [],
887 ''
888 ],
889 'number without operator returns array with plus and number' => [
890 [['+', 42]],
891 '42'
892 ],
893 'two numbers with asterisk return first number with plus and second number with asterisk' => [
894 [['+', 42], ['*', 31]],
895 '42 * 31'
896 ]
897 ];
898 }
899
900 /**
901 * @test
902 * @dataProvider splitCalcDataProvider
903 */
904 public function splitCalcCorrectlySplitsExpression($expected, $expression)
905 {
906 $this->assertEquals($expected, GeneralUtility::splitCalc($expression, '+-*/'));
907 }
908
909 ///////////////////////////////
910 // Tests concerning htmlspecialchars_decode
911 ///////////////////////////////
912 /**
913 * @test
914 */
915 public function htmlspecialcharsDecodeReturnsDecodedString()
916 {
917 $string = '<typo3 version="6.0">&nbsp;</typo3>';
918 $encoded = htmlspecialchars($string);
919 $decoded = htmlspecialchars_decode($encoded);
920 $this->assertEquals($string, $decoded);
921 }
922
923 //////////////////////////////////
924 // Tests concerning validEmail
925 //////////////////////////////////
926 /**
927 * Data provider for valid validEmail's
928 *
929 * @return array Valid email addresses
930 */
931 public function validEmailValidDataProvider()
932 {
933 return [
934 'short mail address' => ['a@b.c'],
935 'simple mail address' => ['test@example.com'],
936 'uppercase characters' => ['QWERTYUIOPASDFGHJKLZXCVBNM@QWERTYUIOPASDFGHJKLZXCVBNM.NET'],
937 'equal sign in local part' => ['test=mail@example.com'],
938 'dash in local part' => ['test-mail@example.com'],
939 'plus in local part' => ['test+mail@example.com'],
940 'question mark in local part' => ['test?mail@example.com'],
941 'slash in local part' => ['foo/bar@example.com'],
942 'hash in local part' => ['foo#bar@example.com'],
943 'dot in local part' => ['firstname.lastname@employee.2something.com'],
944 'dash as local part' => ['-@foo.com'],
945 'umlauts in domain part' => ['foo@äöüfoo.com']
946 ];
947 }
948
949 /**
950 * @test
951 * @dataProvider validEmailValidDataProvider
952 */
953 public function validEmailReturnsTrueForValidMailAddress($address)
954 {
955 $this->assertTrue(GeneralUtility::validEmail($address));
956 }
957
958 /**
959 * Data provider for invalid validEmail's
960 *
961 * @return array Invalid email addresses
962 */
963 public function validEmailInvalidDataProvider()
964 {
965 return [
966 'empty string' => [''],
967 'empty array' => [[]],
968 'integer' => [42],
969 'float' => [42.23],
970 'array' => [['foo']],
971 'object' => [new \stdClass()],
972 '@ sign only' => ['@'],
973 'string longer than 320 characters' => [str_repeat('0123456789', 33)],
974 'duplicate @' => ['test@@example.com'],
975 'duplicate @ combined with further special characters in local part' => ['test!.!@#$%^&*@example.com'],
976 'opening parenthesis in local part' => ['foo(bar@example.com'],
977 'closing parenthesis in local part' => ['foo)bar@example.com'],
978 'opening square bracket in local part' => ['foo[bar@example.com'],
979 'closing square bracket as local part' => [']@example.com'],
980 'top level domain only' => ['test@com'],
981 'dash as second level domain' => ['foo@-.com'],
982 'domain part starting with dash' => ['foo@-foo.com'],
983 'domain part ending with dash' => ['foo@foo-.com'],
984 'number as top level domain' => ['foo@bar.123'],
985 'dot at beginning of domain part' => ['test@.com'],
986 'local part ends with dot' => ['e.x.a.m.p.l.e.@example.com'],
987 'umlauts in local part' => ['äöüfoo@bar.com'],
988 'trailing whitespace' => ['test@example.com '],
989 'trailing carriage return' => ['test@example.com' . CR],
990 'trailing linefeed' => ['test@example.com' . LF],
991 'trailing carriage return linefeed' => ['test@example.com' . CRLF],
992 'trailing tab' => ['test@example.com' . "\t"],
993 'prohibited input characters' => ['“mailto:test@example.com”'],
994 ];
995 }
996
997 /**
998 * @test
999 * @dataProvider validEmailInvalidDataProvider
1000 */
1001 public function validEmailReturnsFalseForInvalidMailAddress($address)
1002 {
1003 $this->assertFalse(GeneralUtility::validEmail($address));
1004 }
1005
1006 //////////////////////////////////
1007 // Tests concerning intExplode
1008 //////////////////////////////////
1009 /**
1010 * @test
1011 */
1012 public function intExplodeConvertsStringsToInteger()
1013 {
1014 $testString = '1,foo,2';
1015 $expectedArray = [1, 0, 2];
1016 $actualArray = GeneralUtility::intExplode(',', $testString);
1017 $this->assertEquals($expectedArray, $actualArray);
1018 }
1019
1020 //////////////////////////////////
1021 // Tests concerning implodeArrayForUrl / explodeUrl2Array
1022 //////////////////////////////////
1023 /**
1024 * Data provider for implodeArrayForUrlBuildsValidParameterString
1025 *
1026 * @return array
1027 */
1028 public function implodeArrayForUrlDataProvider()
1029 {
1030 $valueArray = ['one' => '√', 'two' => 2];
1031 return [
1032 'Empty input' => ['foo', [], ''],
1033 'String parameters' => ['foo', $valueArray, '&foo[one]=%E2%88%9A&foo[two]=2'],
1034 'Nested array parameters' => ['foo', [$valueArray], '&foo[0][one]=%E2%88%9A&foo[0][two]=2'],
1035 'Keep blank parameters' => ['foo', ['one' => '√', ''], '&foo[one]=%E2%88%9A&foo[0]=']
1036 ];
1037 }
1038
1039 /**
1040 * @test
1041 * @dataProvider implodeArrayForUrlDataProvider
1042 */
1043 public function implodeArrayForUrlBuildsValidParameterString($name, $input, $expected)
1044 {
1045 $this->assertSame($expected, GeneralUtility::implodeArrayForUrl($name, $input));
1046 }
1047
1048 /**
1049 * @test
1050 */
1051 public function implodeArrayForUrlCanSkipEmptyParameters()
1052 {
1053 $input = ['one' => '√', ''];
1054 $expected = '&foo[one]=%E2%88%9A';
1055 $this->assertSame($expected, GeneralUtility::implodeArrayForUrl('foo', $input, '', true));
1056 }
1057
1058 /**
1059 * @test
1060 */
1061 public function implodeArrayForUrlCanUrlEncodeKeyNames()
1062 {
1063 $input = ['one' => '√', ''];
1064 $expected = '&foo%5Bone%5D=%E2%88%9A&foo%5B0%5D=';
1065 $this->assertSame($expected, GeneralUtility::implodeArrayForUrl('foo', $input, '', false, true));
1066 }
1067
1068 /**
1069 * @test
1070 * @dataProvider explodeUrl2ArrayDataProvider
1071 */
1072 public function explodeUrl2ArrayTransformsParameterStringToFlatArray($input, $expected)
1073 {
1074 $this->assertEquals($expected, GeneralUtility::explodeUrl2Array($input));
1075 }
1076
1077 /**
1078 * Data provider for explodeUrl2ArrayTransformsParameterStringToFlatArray
1079 *
1080 * @return array
1081 */
1082 public function explodeUrl2ArrayDataProvider()
1083 {
1084 return [
1085 'Empty string' => ['', []],
1086 'Simple parameter string' => ['&one=%E2%88%9A&two=2', ['one' => '√', 'two' => 2]],
1087 'Nested parameter string' => ['&foo[one]=%E2%88%9A&two=2', ['foo[one]' => '√', 'two' => 2]]
1088 ];
1089 }
1090
1091 //////////////////////////////////
1092 // Tests concerning compileSelectedGetVarsFromArray
1093 //////////////////////////////////
1094 /**
1095 * @test
1096 */
1097 public function compileSelectedGetVarsFromArrayFiltersIncomingData()
1098 {
1099 $filter = 'foo,bar';
1100 $getArray = ['foo' => 1, 'cake' => 'lie'];
1101 $expected = ['foo' => 1];
1102 $result = GeneralUtility::compileSelectedGetVarsFromArray($filter, $getArray, false);
1103 $this->assertSame($expected, $result);
1104 }
1105
1106 /**
1107 * @test
1108 */
1109 public function compileSelectedGetVarsFromArrayUsesGetPostDataFallback()
1110 {
1111 $_GET['bar'] = '2';
1112 $filter = 'foo,bar';
1113 $getArray = ['foo' => 1, 'cake' => 'lie'];
1114 $expected = ['foo' => 1, 'bar' => '2'];
1115 $result = GeneralUtility::compileSelectedGetVarsFromArray($filter, $getArray, true);
1116 $this->assertSame($expected, $result);
1117 }
1118
1119 //////////////////////////////////
1120 // Tests concerning revExplode
1121 //////////////////////////////////
1122
1123 /**
1124 * @return array
1125 */
1126 public function revExplodeDataProvider()
1127 {
1128 return [
1129 'limit 0 should return unexploded string' => [
1130 ':',
1131 'my:words:here',
1132 0,
1133 ['my:words:here']
1134 ],
1135 'limit 1 should return unexploded string' => [
1136 ':',
1137 'my:words:here',
1138 1,
1139 ['my:words:here']
1140 ],
1141 'limit 2 should return two pieces' => [
1142 ':',
1143 'my:words:here',
1144 2,
1145 ['my:words', 'here']
1146 ],
1147 'limit 3 should return unexploded string' => [
1148 ':',
1149 'my:words:here',
1150 3,
1151 ['my', 'words', 'here']
1152 ],
1153 'limit 0 should return unexploded string if no delimiter is contained' => [
1154 ':',
1155 'mywordshere',
1156 0,
1157 ['mywordshere']
1158 ],
1159 'limit 1 should return unexploded string if no delimiter is contained' => [
1160 ':',
1161 'mywordshere',
1162 1,
1163 ['mywordshere']
1164 ],
1165 'limit 2 should return unexploded string if no delimiter is contained' => [
1166 ':',
1167 'mywordshere',
1168 2,
1169 ['mywordshere']
1170 ],
1171 'limit 3 should return unexploded string if no delimiter is contained' => [
1172 ':',
1173 'mywordshere',
1174 3,
1175 ['mywordshere']
1176 ],
1177 'multi character delimiter is handled properly with limit 2' => [
1178 '[]',
1179 'a[b][c][d]',
1180 2,
1181 ['a[b][c', 'd]']
1182 ],
1183 'multi character delimiter is handled properly with limit 3' => [
1184 '[]',
1185 'a[b][c][d]',
1186 3,
1187 ['a[b', 'c', 'd]']
1188 ],
1189 ];
1190 }
1191
1192 /**
1193 * @test
1194 * @dataProvider revExplodeDataProvider
1195 */
1196 public function revExplodeCorrectlyExplodesStringForGivenPartsCount($delimiter, $testString, $count, $expectedArray)
1197 {
1198 $actualArray = GeneralUtility::revExplode($delimiter, $testString, $count);
1199 $this->assertEquals($expectedArray, $actualArray);
1200 }
1201
1202 /**
1203 * @test
1204 */
1205 public function revExplodeRespectsLimitThreeWhenExploding()
1206 {
1207 $testString = 'even:more:of:my:words:here';
1208 $expectedArray = ['even:more:of:my', 'words', 'here'];
1209 $actualArray = GeneralUtility::revExplode(':', $testString, 3);
1210 $this->assertEquals($expectedArray, $actualArray);
1211 }
1212
1213 //////////////////////////////////
1214 // Tests concerning trimExplode
1215 //////////////////////////////////
1216 /**
1217 * @test
1218 * @dataProvider trimExplodeReturnsCorrectResultDataProvider
1219 *
1220 * @param string $delimiter
1221 * @param string $testString
1222 * @param bool $removeEmpty
1223 * @param int $limit
1224 * @param array $expectedResult
1225 */
1226 public function trimExplodeReturnsCorrectResult($delimiter, $testString, $removeEmpty, $limit, $expectedResult)
1227 {
1228 $this->assertSame($expectedResult, GeneralUtility::trimExplode($delimiter, $testString, $removeEmpty, $limit));
1229 }
1230
1231 /**
1232 * @return array
1233 */
1234 public function trimExplodeReturnsCorrectResultDataProvider()
1235 {
1236 return [
1237 'spaces at element start and end' => [
1238 ',',
1239 ' a , b , c ,d ,, e,f,',
1240 false,
1241 0,
1242 ['a', 'b', 'c', 'd', '', 'e', 'f', '']
1243 ],
1244 'removes newline' => [
1245 ',',
1246 ' a , b , ' . LF . ' ,d ,, e,f,',
1247 true,
1248 0,
1249 ['a', 'b', 'd', 'e', 'f']
1250 ],
1251 'removes empty elements' => [
1252 ',',
1253 'a , b , c , ,d ,, ,e,f,',
1254 true,
1255 0,
1256 ['a', 'b', 'c', 'd', 'e', 'f']
1257 ],
1258 'keeps remaining results with empty items after reaching limit with positive parameter' => [
1259 ',',
1260 ' a , b , c , , d,, ,e ',
1261 false,
1262 3,
1263 ['a', 'b', 'c , , d,, ,e']
1264 ],
1265 'keeps remaining results without empty items after reaching limit with positive parameter' => [
1266 ',',
1267 ' a , b , c , , d,, ,e ',
1268 true,
1269 3,
1270 ['a', 'b', 'c , d,e']
1271 ],
1272 'keeps remaining results with empty items after reaching limit with negative parameter' => [
1273 ',',
1274 ' a , b , c , d, ,e, f , , ',
1275 false,
1276 -3,
1277 ['a', 'b', 'c', 'd', '', 'e']
1278 ],
1279 'keeps remaining results without empty items after reaching limit with negative parameter' => [
1280 ',',
1281 ' a , b , c , d, ,e, f , , ',
1282 true,
1283 -3,
1284 ['a', 'b', 'c']
1285 ],
1286 'returns exact results without reaching limit with positive parameter' => [
1287 ',',
1288 ' a , b , , c , , , ',
1289 true,
1290 4,
1291 ['a', 'b', 'c']
1292 ],
1293 'keeps zero as string' => [
1294 ',',
1295 'a , b , c , ,d ,, ,e,f, 0 ,',
1296 true,
1297 0,
1298 ['a', 'b', 'c', 'd', 'e', 'f', '0']
1299 ],
1300 'keeps whitespace inside elements' => [
1301 ',',
1302 'a , b , c , ,d ,, ,e,f, g h ,',
1303 true,
1304 0,
1305 ['a', 'b', 'c', 'd', 'e', 'f', 'g h']
1306 ],
1307 'can use internal regex delimiter as explode delimiter' => [
1308 '/',
1309 'a / b / c / /d // /e/f/ g h /',
1310 true,
1311 0,
1312 ['a', 'b', 'c', 'd', 'e', 'f', 'g h']
1313 ],
1314 'can use whitespaces as delimiter' => [
1315 ' ',
1316 '* * * * *',
1317 true,
1318 0,
1319 ['*', '*', '*', '*', '*']
1320 ],
1321 'can use words as delimiter' => [
1322 'All',
1323 'HelloAllTogether',
1324 true,
1325 0,
1326 ['Hello', 'Together']
1327 ],
1328 'can use word with appended and prepended spaces as delimiter' => [
1329 ' all ',
1330 'Hello all together',
1331 true,
1332 0,
1333 ['Hello', 'together']
1334 ],
1335 'can use word with appended and prepended spaces as delimiter and do not remove empty' => [
1336 ' all ',
1337 'Hello all together all there all all are all none',
1338 false,
1339 0,
1340 ['Hello', 'together', 'there', '', 'are', 'none']
1341 ],
1342 'can use word with appended and prepended spaces as delimiter, do not remove empty and limit' => [
1343 ' all ',
1344 'Hello all together all there all all are all none',
1345 false,
1346 5,
1347 ['Hello', 'together', 'there', '', 'are all none']
1348 ],
1349 'can use word with appended and prepended spaces as delimiter, do not remove empty, limit and multiple delimiter in last' => [
1350 ' all ',
1351 'Hello all together all there all all are all none',
1352 false,
1353 4,
1354 ['Hello', 'together', 'there', 'all are all none']
1355 ],
1356 'can use word with appended and prepended spaces as delimiter, remove empty and limit' => [
1357 ' all ',
1358 'Hello all together all there all all are all none',
1359 true,
1360 4,
1361 ['Hello', 'together', 'there', 'are all none']
1362 ],
1363 'can use word with appended and prepended spaces as delimiter, remove empty and limit and multiple delimiter in last' => [
1364 ' all ',
1365 'Hello all together all there all all are all none',
1366 true,
1367 5,
1368 ['Hello', 'together', 'there', 'are' , 'none']
1369 ],
1370 'can use words as delimiter and do not remove empty' => [
1371 'all there',
1372 'Helloall theretogether all there all there are all there none',
1373 false,
1374 0,
1375 ['Hello', 'together', '', 'are', 'none']
1376 ],
1377 'can use words as delimiter, do not remove empty and limit' => [
1378 'all there',
1379 'Helloall theretogether all there all there are all there none',
1380 false,
1381 4,
1382 ['Hello', 'together', '', 'are all there none']
1383 ],
1384 'can use words as delimiter, do not remove empty, limit and multiple delimiter in last' => [
1385 'all there',
1386 'Helloall theretogether all there all there are all there none',
1387 false,
1388 3,
1389 ['Hello', 'together', 'all there are all there none']
1390 ],
1391 'can use words as delimiter, remove empty' => [
1392 'all there',
1393 'Helloall theretogether all there all there are all there none',
1394 true,
1395 0,
1396 ['Hello', 'together', 'are', 'none']
1397 ],
1398 'can use words as delimiter, remove empty and limit' => [
1399 'all there',
1400 'Helloall theretogether all there all there are all there none',
1401 true,
1402 3,
1403 ['Hello', 'together', 'are all there none']
1404 ],
1405 'can use words as delimiter, remove empty and limit and multiple delimiter in last' => [
1406 'all there',
1407 'Helloall theretogether all there all there are all there none',
1408 true,
1409 4,
1410 ['Hello', 'together', 'are' , 'none']
1411 ],
1412 'can use new line as delimiter' => [
1413 LF,
1414 "Hello\nall\ntogether",
1415 true,
1416 0,
1417 ['Hello', 'all', 'together']
1418 ],
1419 'works with whitespace separator' => [
1420 "\t",
1421 " a b \t c \t \t d \t e \t u j \t s",
1422 false,
1423 0,
1424 ['a b', 'c', '', 'd', 'e', 'u j', 's']
1425 ],
1426 'works with whitespace separator and limit' => [
1427 "\t",
1428 " a b \t c \t \t d \t e \t u j \t s",
1429 false,
1430 4,
1431 ['a b', 'c', '', "d \t e \t u j \t s"]
1432 ],
1433 'works with whitespace separator and remove empty' => [
1434 "\t",
1435 " a b \t c \t \t d \t e \t u j \t s",
1436 true,
1437 0,
1438 ['a b', 'c', 'd', 'e', 'u j', 's']
1439 ],
1440 'works with whitespace separator remove empty and limit' => [
1441 "\t",
1442 " a b \t c \t \t d \t e \t u j \t s",
1443 true,
1444 3,
1445 ['a b', 'c', "d \t e \t u j \t s"]
1446 ],
1447 ];
1448 }
1449
1450 //////////////////////////////////
1451 // Tests concerning getBytesFromSizeMeasurement
1452 //////////////////////////////////
1453 /**
1454 * Data provider for getBytesFromSizeMeasurement
1455 *
1456 * @return array expected value, input string
1457 */
1458 public function getBytesFromSizeMeasurementDataProvider()
1459 {
1460 return [
1461 '100 kilo Bytes' => ['102400', '100k'],
1462 '100 mega Bytes' => ['104857600', '100m'],
1463 '100 giga Bytes' => ['107374182400', '100g']
1464 ];
1465 }
1466
1467 /**
1468 * @test
1469 * @dataProvider getBytesFromSizeMeasurementDataProvider
1470 */
1471 public function getBytesFromSizeMeasurementCalculatesCorrectByteValue($expected, $byteString)
1472 {
1473 $this->assertEquals($expected, GeneralUtility::getBytesFromSizeMeasurement($byteString));
1474 }
1475
1476 //////////////////////////////////
1477 // Tests concerning getIndpEnv
1478 //////////////////////////////////
1479 /**
1480 * @test
1481 */
1482 public function getIndpEnvTypo3SitePathReturnNonEmptyString()
1483 {
1484 $this->assertTrue(strlen(GeneralUtility::getIndpEnv('TYPO3_SITE_PATH')) >= 1);
1485 }
1486
1487 /**
1488 * @test
1489 */
1490 public function getIndpEnvTypo3SitePathReturnsStringStartingWithSlash()
1491 {
1492 $_SERVER['SCRIPT_NAME'] = '/typo3/';
1493 $result = GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
1494 $this->assertEquals('/', $result[0]);
1495 }
1496
1497 /**
1498 * @test
1499 * @requires OSFAMILY Windows
1500 */
1501 public function getIndpEnvTypo3SitePathReturnsStringStartingWithDrive()
1502 {
1503 $result = GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
1504 $this->assertRegExp('/^[a-z]:\//i', $result);
1505 }
1506
1507 /**
1508 * @test
1509 */
1510 public function getIndpEnvTypo3SitePathReturnsStringEndingWithSlash()
1511 {
1512 $result = GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
1513 $this->assertEquals('/', $result[strlen($result) - 1]);
1514 }
1515
1516 /**
1517 * @return array
1518 */
1519 public static function hostnameAndPortDataProvider()
1520 {
1521 return [
1522 'localhost ipv4 without port' => ['127.0.0.1', '127.0.0.1', ''],
1523 'localhost ipv4 with port' => ['127.0.0.1:81', '127.0.0.1', '81'],
1524 'localhost ipv6 without port' => ['[::1]', '[::1]', ''],
1525 'localhost ipv6 with port' => ['[::1]:81', '[::1]', '81'],
1526 'ipv6 without port' => ['[2001:DB8::1]', '[2001:DB8::1]', ''],
1527 'ipv6 with port' => ['[2001:DB8::1]:81', '[2001:DB8::1]', '81'],
1528 'hostname without port' => ['lolli.did.this', 'lolli.did.this', ''],
1529 'hostname with port' => ['lolli.did.this:42', 'lolli.did.this', '42']
1530 ];
1531 }
1532
1533 /**
1534 * @test
1535 * @dataProvider hostnameAndPortDataProvider
1536 */
1537 public function getIndpEnvTypo3HostOnlyParsesHostnamesAndIpAdresses($httpHost, $expectedIp)
1538 {
1539 $_SERVER['HTTP_HOST'] = $httpHost;
1540 $this->assertEquals($expectedIp, GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY'));
1541 }
1542
1543 /**
1544 * @test
1545 */
1546 public function isAllowedHostHeaderValueReturnsFalseIfTrusedHostsIsNotConfigured()
1547 {
1548 unset($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern']);
1549 $this->assertFalse(GeneralUtilityFixture::isAllowedHostHeaderValue('evil.foo.bar'));
1550 }
1551
1552 /**
1553 * @return array
1554 */
1555 public static function hostnamesMatchingTrustedHostsConfigurationDataProvider()
1556 {
1557 return [
1558 'hostname without port matching' => ['lolli.did.this', '.*\.did\.this'],
1559 'other hostname without port matching' => ['helmut.did.this', '.*\.did\.this'],
1560 'two different hostnames without port matching 1st host' => ['helmut.is.secure', '(helmut\.is\.secure|lolli\.is\.secure)'],
1561 'two different hostnames without port matching 2nd host' => ['lolli.is.secure', '(helmut\.is\.secure|lolli\.is\.secure)'],
1562 'hostname with port matching' => ['lolli.did.this:42', '.*\.did\.this:42'],
1563 'hostnames are case insensitive 1' => ['lolli.DID.this:42', '.*\.did.this:42'],
1564 'hostnames are case insensitive 2' => ['lolli.did.this:42', '.*\.DID.this:42'],
1565 ];
1566 }
1567
1568 /**
1569 * @return array
1570 */
1571 public static function hostnamesNotMatchingTrustedHostsConfigurationDataProvider()
1572 {
1573 return [
1574 'hostname without port' => ['lolli.did.this', 'helmut\.did\.this'],
1575 'hostname with port, but port not allowed' => ['lolli.did.this:42', 'helmut\.did\.this'],
1576 'two different hostnames in pattern but host header starts with different value #1' => ['sub.helmut.is.secure', '(helmut\.is\.secure|lolli\.is\.secure)'],
1577 'two different hostnames in pattern but host header starts with different value #2' => ['sub.lolli.is.secure', '(helmut\.is\.secure|lolli\.is\.secure)'],
1578 'two different hostnames in pattern but host header ends with different value #1' => ['helmut.is.secure.tld', '(helmut\.is\.secure|lolli\.is\.secure)'],
1579 'two different hostnames in pattern but host header ends with different value #2' => ['lolli.is.secure.tld', '(helmut\.is\.secure|lolli\.is\.secure)'],
1580 ];
1581 }
1582
1583 /**
1584 * @param string $httpHost HTTP_HOST string
1585 * @param string $hostNamePattern trusted hosts pattern
1586 * @test
1587 * @dataProvider hostnamesMatchingTrustedHostsConfigurationDataProvider
1588 */
1589 public function isAllowedHostHeaderValueReturnsTrueIfHostValueMatches($httpHost, $hostNamePattern)
1590 {
1591 $GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] = $hostNamePattern;
1592 $this->assertTrue(GeneralUtilityFixture::isAllowedHostHeaderValue($httpHost));
1593 }
1594
1595 /**
1596 * @param string $httpHost HTTP_HOST string
1597 * @param string $hostNamePattern trusted hosts pattern
1598 * @test
1599 * @dataProvider hostnamesNotMatchingTrustedHostsConfigurationDataProvider
1600 */
1601 public function isAllowedHostHeaderValueReturnsFalseIfHostValueMatches($httpHost, $hostNamePattern)
1602 {
1603 $GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] = $hostNamePattern;
1604 $this->assertFalse(GeneralUtilityFixture::isAllowedHostHeaderValue($httpHost));
1605 }
1606
1607 public function serverNamePatternDataProvider()
1608 {
1609 return [
1610 'host value matches server name and server port is default http' => [
1611 'httpHost' => 'secure.web.server',
1612 'serverName' => 'secure.web.server',
1613 'isAllowed' => true,
1614 'serverPort' => '80',
1615 'ssl' => 'Off',
1616 ],
1617 'host value matches server name if compared case insensitive 1' => [
1618 'httpHost' => 'secure.web.server',
1619 'serverName' => 'secure.WEB.server',
1620 'isAllowed' => true,
1621 ],
1622 'host value matches server name if compared case insensitive 2' => [
1623 'httpHost' => 'secure.WEB.server',
1624 'serverName' => 'secure.web.server',
1625 'isAllowed' => true,
1626 ],
1627 'host value matches server name and server port is default https' => [
1628 'httpHost' => 'secure.web.server',
1629 'serverName' => 'secure.web.server',
1630 'isAllowed' => true,
1631 'serverPort' => '443',
1632 'ssl' => 'On',
1633 ],
1634 'host value matches server name and server port' => [
1635 'httpHost' => 'secure.web.server:88',
1636 'serverName' => 'secure.web.server',
1637 'isAllowed' => true,
1638 'serverPort' => '88',
1639 ],
1640 'host value matches server name case insensitive 1 and server port' => [
1641 'httpHost' => 'secure.WEB.server:88',
1642 'serverName' => 'secure.web.server',
1643 'isAllowed' => true,
1644 'serverPort' => '88',
1645 ],
1646 'host value matches server name case insensitive 2 and server port' => [
1647 'httpHost' => 'secure.web.server:88',
1648 'serverName' => 'secure.WEB.server',
1649 'isAllowed' => true,
1650 'serverPort' => '88',
1651 ],
1652 'host value is ipv6 but matches server name and server port' => [
1653 'httpHost' => '[::1]:81',
1654 'serverName' => '[::1]',
1655 'isAllowed' => true,
1656 'serverPort' => '81',
1657 ],
1658 'host value does not match server name' => [
1659 'httpHost' => 'insecure.web.server',
1660 'serverName' => 'secure.web.server',
1661 'isAllowed' => false,
1662 ],
1663 'host value does not match server port' => [
1664 'httpHost' => 'secure.web.server:88',
1665 'serverName' => 'secure.web.server',
1666 'isAllowed' => false,
1667 'serverPort' => '89',
1668 ],
1669 'host value has default port that does not match server port' => [
1670 'httpHost' => 'secure.web.server',
1671 'serverName' => 'secure.web.server',
1672 'isAllowed' => false,
1673 'serverPort' => '81',
1674 'ssl' => 'Off',
1675 ],
1676 'host value has default port that does not match server ssl port' => [
1677 'httpHost' => 'secure.web.server',
1678 'serverName' => 'secure.web.server',
1679 'isAllowed' => false,
1680 'serverPort' => '444',
1681 'ssl' => 'On',
1682 ],
1683 ];
1684 }
1685
1686 /**
1687 * @param string $httpHost
1688 * @param string $serverName
1689 * @param bool $isAllowed
1690 * @param string $serverPort
1691 * @param string $ssl
1692 *
1693 * @test
1694 * @dataProvider serverNamePatternDataProvider
1695 */
1696 public function isAllowedHostHeaderValueWorksCorrectlyWithWithServerNamePattern($httpHost, $serverName, $isAllowed, $serverPort = '80', $ssl = 'Off')
1697 {
1698 $GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] = GeneralUtility::ENV_TRUSTED_HOSTS_PATTERN_SERVER_NAME;
1699 $_SERVER['SERVER_NAME'] = $serverName;
1700 $_SERVER['SERVER_PORT'] = $serverPort;
1701 $_SERVER['HTTPS'] = $ssl;
1702 $this->assertSame($isAllowed, GeneralUtilityFixture::isAllowedHostHeaderValue($httpHost));
1703 }
1704
1705 /**
1706 * @test
1707 */
1708 public function allGetIndpEnvCallsRelatedToHostNamesCallIsAllowedHostHeaderValue()
1709 {
1710 GeneralUtilityFixture::getIndpEnv('HTTP_HOST');
1711 GeneralUtility::flushInternalRuntimeCaches();
1712 GeneralUtilityFixture::getIndpEnv('TYPO3_HOST_ONLY');
1713 GeneralUtility::flushInternalRuntimeCaches();
1714 GeneralUtilityFixture::getIndpEnv('TYPO3_REQUEST_HOST');
1715 GeneralUtility::flushInternalRuntimeCaches();
1716 GeneralUtilityFixture::getIndpEnv('TYPO3_REQUEST_URL');
1717 $this->assertSame(4, GeneralUtilityFixture::$isAllowedHostHeaderValueCallCount);
1718 }
1719
1720 /**
1721 * @param string $httpHost HTTP_HOST string
1722 * @param string $hostNamePattern trusted hosts pattern
1723 * @test
1724 * @dataProvider hostnamesNotMatchingTrustedHostsConfigurationDataProvider
1725 */
1726 public function getIndpEnvForHostThrowsExceptionForNotAllowedHostnameValues($httpHost, $hostNamePattern)
1727 {
1728 $this->expectException(\UnexpectedValueException::class);
1729 $this->expectExceptionCode(1396795884);
1730 $_SERVER['HTTP_HOST'] = $httpHost;
1731 $GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] = $hostNamePattern;
1732 GeneralUtilityFixture::getIndpEnv('HTTP_HOST');
1733 }
1734
1735 /**
1736 * @param string $httpHost HTTP_HOST string
1737 * @param string $hostNamePattern trusted hosts pattern (not used in this test currently)
1738 * @test
1739 * @dataProvider hostnamesNotMatchingTrustedHostsConfigurationDataProvider
1740 */
1741 public function getIndpEnvForHostAllowsAllHostnameValuesIfHostPatternIsSetToAllowAll($httpHost, $hostNamePattern)
1742 {
1743 $_SERVER['HTTP_HOST'] = $httpHost;
1744 $GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] = GeneralUtility::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL;
1745 $this->assertSame($httpHost, GeneralUtility::getIndpEnv('HTTP_HOST'));
1746 }
1747
1748 /**
1749 * @test
1750 * @dataProvider hostnameAndPortDataProvider
1751 */
1752 public function getIndpEnvTypo3PortParsesHostnamesAndIpAdresses($httpHost, $dummy, $expectedPort)
1753 {
1754 $_SERVER['HTTP_HOST'] = $httpHost;
1755 $this->assertEquals($expectedPort, GeneralUtility::getIndpEnv('TYPO3_PORT'));
1756 }
1757
1758 //////////////////////////////////
1759 // Tests concerning underscoredToUpperCamelCase
1760 //////////////////////////////////
1761 /**
1762 * Data provider for underscoredToUpperCamelCase
1763 *
1764 * @return array expected, input string
1765 */
1766 public function underscoredToUpperCamelCaseDataProvider()
1767 {
1768 return [
1769 'single word' => ['Blogexample', 'blogexample'],
1770 'multiple words' => ['BlogExample', 'blog_example']
1771 ];
1772 }
1773
1774 /**
1775 * @test
1776 * @dataProvider underscoredToUpperCamelCaseDataProvider
1777 */
1778 public function underscoredToUpperCamelCase($expected, $inputString)
1779 {
1780 $this->assertEquals($expected, GeneralUtility::underscoredToUpperCamelCase($inputString));
1781 }
1782
1783 //////////////////////////////////
1784 // Tests concerning underscoredToLowerCamelCase
1785 //////////////////////////////////
1786 /**
1787 * Data provider for underscoredToLowerCamelCase
1788 *
1789 * @return array expected, input string
1790 */
1791 public function underscoredToLowerCamelCaseDataProvider()
1792 {
1793 return [
1794 'single word' => ['minimalvalue', 'minimalvalue'],
1795 'multiple words' => ['minimalValue', 'minimal_value']
1796 ];
1797 }
1798
1799 /**
1800 * @test
1801 * @dataProvider underscoredToLowerCamelCaseDataProvider
1802 */
1803 public function underscoredToLowerCamelCase($expected, $inputString)
1804 {
1805 $this->assertEquals($expected, GeneralUtility::underscoredToLowerCamelCase($inputString));
1806 }
1807
1808 //////////////////////////////////
1809 // Tests concerning camelCaseToLowerCaseUnderscored
1810 //////////////////////////////////
1811 /**
1812 * Data provider for camelCaseToLowerCaseUnderscored
1813 *
1814 * @return array expected, input string
1815 */
1816 public function camelCaseToLowerCaseUnderscoredDataProvider()
1817 {
1818 return [
1819 'single word' => ['blogexample', 'blogexample'],
1820 'single word starting upper case' => ['blogexample', 'Blogexample'],
1821 'two words starting lower case' => ['minimal_value', 'minimalValue'],
1822 'two words starting upper case' => ['blog_example', 'BlogExample']
1823 ];
1824 }
1825
1826 /**
1827 * @test
1828 * @dataProvider camelCaseToLowerCaseUnderscoredDataProvider
1829 */
1830 public function camelCaseToLowerCaseUnderscored($expected, $inputString)
1831 {
1832 $this->assertEquals($expected, GeneralUtility::camelCaseToLowerCaseUnderscored($inputString));
1833 }
1834
1835 //////////////////////////////////
1836 // Tests concerning isValidUrl
1837 //////////////////////////////////
1838 /**
1839 * Data provider for valid isValidUrl's
1840 *
1841 * @return array Valid resource
1842 */
1843 public function validUrlValidResourceDataProvider()
1844 {
1845 return [
1846 'http' => ['http://www.example.org/'],
1847 'http without trailing slash' => ['http://qwe'],
1848 'http directory with trailing slash' => ['http://www.example/img/dir/'],
1849 'http directory without trailing slash' => ['http://www.example/img/dir'],
1850 'http index.html' => ['http://example.com/index.html'],
1851 'http index.php' => ['http://www.example.com/index.php'],
1852 'http test.png' => ['http://www.example/img/test.png'],
1853 'http username password querystring and ancher' => ['https://user:pw@www.example.org:80/path?arg=value#fragment'],
1854 'file' => ['file:///tmp/test.c'],
1855 'file directory' => ['file://foo/bar'],
1856 'ftp directory' => ['ftp://ftp.example.com/tmp/'],
1857 'mailto' => ['mailto:foo@bar.com'],
1858 'news' => ['news:news.php.net'],
1859 'telnet' => ['telnet://192.0.2.16:80/'],
1860 'ldap' => ['ldap://[2001:db8::7]/c=GB?objectClass?one'],
1861 'http punycode domain name' => ['http://www.xn--bb-eka.at'],
1862 'http punicode subdomain' => ['http://xn--h-zfa.oebb.at'],
1863 'http domain-name umlauts' => ['http://www.öbb.at'],
1864 'http subdomain umlauts' => ['http://äh.oebb.at'],
1865 ];
1866 }
1867
1868 /**
1869 * @test
1870 * @dataProvider validUrlValidResourceDataProvider
1871 */
1872 public function validURLReturnsTrueForValidResource($url)
1873 {
1874 $this->assertTrue(GeneralUtility::isValidUrl($url));
1875 }
1876
1877 /**
1878 * Data provider for invalid isValidUrl's
1879 *
1880 * @return array Invalid ressource
1881 */
1882 public function isValidUrlInvalidRessourceDataProvider()
1883 {
1884 return [
1885 'http missing colon' => ['http//www.example/wrong/url/'],
1886 'http missing slash' => ['http:/www.example'],
1887 'hostname only' => ['www.example.org/'],
1888 'file missing protocol specification' => ['/tmp/test.c'],
1889 'slash only' => ['/'],
1890 'string http://' => ['http://'],
1891 'string http:/' => ['http:/'],
1892 'string http:' => ['http:'],
1893 'string http' => ['http'],
1894 'empty string' => [''],
1895 'string -1' => ['-1'],
1896 'string array()' => ['array()'],
1897 'random string' => ['qwe'],
1898 'http directory umlauts' => ['http://www.oebb.at/äöü/'],
1899 'prohibited input characters' => ['https://{$unresolved_constant}'],
1900 ];
1901 }
1902
1903 /**
1904 * @test
1905 * @dataProvider isValidUrlInvalidRessourceDataProvider
1906 */
1907 public function validURLReturnsFalseForInvalidRessoure($url)
1908 {
1909 $this->assertFalse(GeneralUtility::isValidUrl($url));
1910 }
1911
1912 //////////////////////////////////
1913 // Tests concerning isOnCurrentHost
1914 //////////////////////////////////
1915 /**
1916 * @test
1917 */
1918 public function isOnCurrentHostReturnsTrueWithCurrentHost()
1919 {
1920 $testUrl = GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL');
1921 $this->assertTrue(GeneralUtility::isOnCurrentHost($testUrl));
1922 }
1923
1924 /**
1925 * Data provider for invalid isOnCurrentHost's
1926 *
1927 * @return array Invalid Hosts
1928 */
1929 public function checkisOnCurrentHostInvalidHosts()
1930 {
1931 return [
1932 'empty string' => [''],
1933 'arbitrary string' => ['arbitrary string'],
1934 'localhost IP' => ['127.0.0.1'],
1935 'relative path' => ['./relpath/file.txt'],
1936 'absolute path' => ['/abspath/file.txt?arg=value'],
1937 'different host' => [GeneralUtility::getIndpEnv('TYPO3_REQUEST_HOST') . '.example.org']
1938 ];
1939 }
1940
1941 ////////////////////////////////////////
1942 // Tests concerning sanitizeLocalUrl
1943 ////////////////////////////////////////
1944 /**
1945 * Data provider for valid sanitizeLocalUrl paths
1946 *
1947 * @return array Valid url
1948 */
1949 public function sanitizeLocalUrlValidPathsDataProvider()
1950 {
1951 return [
1952 'alt_intro.php' => ['alt_intro.php'],
1953 'alt_intro.php?foo=1&bar=2' => ['alt_intro.php?foo=1&bar=2'],
1954 '../index.php' => ['../index.php'],
1955 '../typo3/alt_intro.php' => ['../typo3/alt_intro.php'],
1956 '../~userDirectory/index.php' => ['../~userDirectory/index.php'],
1957 '../typo3/index.php?var1=test-case&var2=~user' => ['../typo3/index.php?var1=test-case&var2=~user'],
1958 Environment::getPublicPath() . '/typo3/alt_intro.php' => [Environment::getPublicPath() . '/typo3/alt_intro.php'],
1959 ];
1960 }
1961
1962 /**
1963 * @test
1964 * @param string $path
1965 * @dataProvider sanitizeLocalUrlValidPathsDataProvider
1966 */
1967 public function sanitizeLocalUrlAcceptsNotEncodedValidPaths($path)
1968 {
1969 $this->assertEquals($path, GeneralUtility::sanitizeLocalUrl($path));
1970 }
1971
1972 /**
1973 * @test
1974 * @param string $path
1975 * @dataProvider sanitizeLocalUrlValidPathsDataProvider
1976 */
1977 public function sanitizeLocalUrlAcceptsEncodedValidPaths($path)
1978 {
1979 $this->assertEquals(rawurlencode($path), GeneralUtility::sanitizeLocalUrl(rawurlencode($path)));
1980 }
1981
1982 /**
1983 * Data provider for valid sanitizeLocalUrl's
1984 *
1985 * @return array Valid url
1986 */
1987 public function sanitizeLocalUrlValidUrlsDataProvider()
1988 {
1989 $host = 'localhost';
1990 $subDirectory = '/cms/';
1991
1992 return [
1993 $subDirectory . 'typo3/alt_intro.php' => [
1994 $subDirectory . 'typo3/alt_intro.php',
1995 $host,
1996 $subDirectory,
1997 ],
1998 $subDirectory . 'index.php' => [
1999 $subDirectory . 'index.php',
2000 $host,
2001 $subDirectory,
2002 ],
2003 'http://' . $host . '/typo3/alt_intro.php' => [
2004 'http://' . $host . '/typo3/alt_intro.php',
2005 $host,
2006 '',
2007 ],
2008 'http://' . $host . $subDirectory . 'typo3/alt_intro.php' => [
2009 'http://' . $host . $subDirectory . 'typo3/alt_intro.php',
2010 $host,
2011 $subDirectory,
2012 ],
2013 ];
2014 }
2015
2016 /**
2017 * @test
2018 * @param string $url
2019 * @param string $host
2020 * @param string $subDirectory
2021 * @dataProvider sanitizeLocalUrlValidUrlsDataProvider
2022 */
2023 public function sanitizeLocalUrlAcceptsNotEncodedValidUrls($url, $host, $subDirectory)
2024 {
2025 $_SERVER['HTTP_HOST'] = $host;
2026 $_SERVER['SCRIPT_NAME'] = $subDirectory . 'typo3/index.php';
2027 $this->assertEquals($url, GeneralUtility::sanitizeLocalUrl($url));
2028 }
2029
2030 /**
2031 * @test
2032 * @param string $url
2033 * @param string $host
2034 * @param string $subDirectory
2035 * @dataProvider sanitizeLocalUrlValidUrlsDataProvider
2036 */
2037 public function sanitizeLocalUrlAcceptsEncodedValidUrls($url, $host, $subDirectory)
2038 {
2039 $_SERVER['HTTP_HOST'] = $host;
2040 $_SERVER['SCRIPT_NAME'] = $subDirectory . 'typo3/index.php';
2041 $this->assertEquals(rawurlencode($url), GeneralUtility::sanitizeLocalUrl(rawurlencode($url)));
2042 }
2043
2044 /**
2045 * Data provider for invalid sanitizeLocalUrl's
2046 *
2047 * @return array Valid url
2048 */
2049 public function sanitizeLocalUrlInvalidDataProvider()
2050 {
2051 return [
2052 'empty string' => [''],
2053 'http domain' => ['http://www.google.de/'],
2054 'https domain' => ['https://www.google.de/'],
2055 'XSS attempt' => ['" onmouseover="alert(123)"'],
2056 'invalid URL, UNC path' => ['\\\\foo\\bar\\'],
2057 'invalid URL, HTML break out attempt' => ['" >blabuubb'],
2058 'base64 encoded string' => ['data:%20text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4='],
2059 ];
2060 }
2061
2062 /**
2063 * @test
2064 * @dataProvider sanitizeLocalUrlInvalidDataProvider
2065 */
2066 public function sanitizeLocalUrlDeniesPlainInvalidUrls($url)
2067 {
2068 $this->assertEquals('', GeneralUtility::sanitizeLocalUrl($url));
2069 }
2070
2071 /**
2072 * @test
2073 * @dataProvider sanitizeLocalUrlInvalidDataProvider
2074 */
2075 public function sanitizeLocalUrlDeniesEncodedInvalidUrls($url)
2076 {
2077 $this->assertEquals('', GeneralUtility::sanitizeLocalUrl(rawurlencode($url)));
2078 }
2079
2080 ////////////////////////////////////////
2081 // Tests concerning unlink_tempfile
2082 ////////////////////////////////////////
2083
2084 /**
2085 * @test
2086 */
2087 public function unlink_tempfileRemovesValidFileInTypo3temp()
2088 {
2089 $fixtureFile = __DIR__ . '/Fixtures/clear.gif';
2090 $testFilename = Environment::getVarPath() . '/tests/' . $this->getUniqueId('test_') . '.gif';
2091 @copy($fixtureFile, $testFilename);
2092 GeneralUtility::unlink_tempfile($testFilename);
2093 $fileExists = file_exists($testFilename);
2094 $this->assertFalse($fileExists);
2095 }
2096
2097 /**
2098 * @test
2099 */
2100 public function unlink_tempfileRemovesHiddenFile()
2101 {
2102 $fixtureFile = __DIR__ . '/Fixtures/clear.gif';
2103 $testFilename = Environment::getVarPath() . '/tests/' . $this->getUniqueId('.test_') . '.gif';
2104 @copy($fixtureFile, $testFilename);
2105 GeneralUtility::unlink_tempfile($testFilename);
2106 $fileExists = file_exists($testFilename);
2107 $this->assertFalse($fileExists);
2108 }
2109
2110 /**
2111 * @test
2112 */
2113 public function unlink_tempfileReturnsTrueIfFileWasRemoved()
2114 {
2115 $fixtureFile = __DIR__ . '/Fixtures/clear.gif';
2116 $testFilename = Environment::getVarPath() . '/tests/' . $this->getUniqueId('test_') . '.gif';
2117 @copy($fixtureFile, $testFilename);
2118 $returnValue = GeneralUtility::unlink_tempfile($testFilename);
2119 $this->assertTrue($returnValue);
2120 }
2121
2122 /**
2123 * @test
2124 */
2125 public function unlink_tempfileReturnsNullIfFileDoesNotExist()
2126 {
2127 $returnValue = GeneralUtility::unlink_tempfile(Environment::getVarPath() . '/tests/' . $this->getUniqueId('i_do_not_exist'));
2128 $this->assertNull($returnValue);
2129 }
2130
2131 /**
2132 * @test
2133 */
2134 public function unlink_tempfileReturnsNullIfFileIsNowWithinTypo3temp()
2135 {
2136 $returnValue = GeneralUtility::unlink_tempfile('/tmp/typo3-unit-test-unlink_tempfile');
2137 $this->assertNull($returnValue);
2138 }
2139
2140 //////////////////////////////////////
2141 // Tests concerning tempnam
2142 //////////////////////////////////////
2143
2144 /**
2145 * @test
2146 */
2147 public function tempnamReturnsPathStartingWithGivenPrefix()
2148 {
2149 $filePath = GeneralUtility::tempnam('foo');
2150 $this->testFilesToDelete[] = $filePath;
2151 $fileName = basename($filePath);
2152 $this->assertStringStartsWith('foo', $fileName);
2153 }
2154
2155 /**
2156 * @test
2157 */
2158 public function tempnamReturnsPathWithoutBackslashes()
2159 {
2160 $filePath = GeneralUtility::tempnam('foo');
2161 $this->testFilesToDelete[] = $filePath;
2162 $this->assertNotContains('\\', $filePath);
2163 }
2164
2165 /**
2166 * @test
2167 */
2168 public function tempnamReturnsAbsolutePathInsideDocumentRoot()
2169 {
2170 $filePath = GeneralUtility::tempnam('foo');
2171 $this->testFilesToDelete[] = $filePath;
2172 $this->assertStringStartsWith(Environment::getPublicPath() . '/', $filePath);
2173 }
2174
2175 //////////////////////////////////////
2176 // Tests concerning removeDotsFromTS
2177 //////////////////////////////////////
2178 /**
2179 * @test
2180 */
2181 public function removeDotsFromTypoScriptSucceedsWithDottedArray()
2182 {
2183 $typoScript = [
2184 'propertyA.' => [
2185 'keyA.' => [
2186 'valueA' => 1
2187 ],
2188 'keyB' => 2
2189 ],
2190 'propertyB' => 3
2191 ];
2192 $expectedResult = [
2193 'propertyA' => [
2194 'keyA' => [
2195 'valueA' => 1
2196 ],
2197 'keyB' => 2
2198 ],
2199 'propertyB' => 3
2200 ];
2201 $this->assertEquals($expectedResult, GeneralUtility::removeDotsFromTS($typoScript));
2202 }
2203
2204 /**
2205 * @test
2206 */
2207 public function removeDotsFromTypoScriptOverridesSubArray()
2208 {
2209 $typoScript = [
2210 'propertyA.' => [
2211 'keyA' => 'getsOverridden',
2212 'keyA.' => [
2213 'valueA' => 1
2214 ],
2215 'keyB' => 2
2216 ],
2217 'propertyB' => 3
2218 ];
2219 $expectedResult = [
2220 'propertyA' => [
2221 'keyA' => [
2222 'valueA' => 1
2223 ],
2224 'keyB' => 2
2225 ],
2226 'propertyB' => 3
2227 ];
2228 $this->assertEquals($expectedResult, GeneralUtility::removeDotsFromTS($typoScript));
2229 }
2230
2231 /**
2232 * @test
2233 */
2234 public function removeDotsFromTypoScriptOverridesWithScalar()
2235 {
2236 $typoScript = [
2237 'propertyA.' => [
2238 'keyA.' => [
2239 'valueA' => 1
2240 ],
2241 'keyA' => 'willOverride',
2242 'keyB' => 2
2243 ],
2244 'propertyB' => 3
2245 ];
2246 $expectedResult = [
2247 'propertyA' => [
2248 'keyA' => 'willOverride',
2249 'keyB' => 2
2250 ],
2251 'propertyB' => 3
2252 ];
2253 $this->assertEquals($expectedResult, GeneralUtility::removeDotsFromTS($typoScript));
2254 }
2255
2256 //////////////////////////////////////
2257 // Tests concerning get_dirs
2258 //////////////////////////////////////
2259 /**
2260 * @test
2261 */
2262 public function getDirsReturnsArrayOfDirectoriesFromGivenDirectory()
2263 {
2264 $directories = GeneralUtility::get_dirs(Environment::getBackendPath() . '/');
2265 $this->assertInternalType(\PHPUnit\Framework\Constraint\IsType::TYPE_ARRAY, $directories);
2266 }
2267
2268 /**
2269 * @test
2270 */
2271 public function getDirsReturnsStringErrorOnPathFailure()
2272 {
2273 $path = 'foo';
2274 $result = GeneralUtility::get_dirs($path);
2275 $expectedResult = 'error';
2276 $this->assertEquals($expectedResult, $result);
2277 }
2278
2279 //////////////////////////////////
2280 // Tests concerning hmac
2281 //////////////////////////////////
2282 /**
2283 * @test
2284 */
2285 public function hmacReturnsHashOfProperLength()
2286 {
2287 $hmac = GeneralUtility::hmac('message');
2288 $this->assertTrue(!empty($hmac) && is_string($hmac));
2289 $this->assertTrue(strlen($hmac) == 40);
2290 }
2291
2292 /**
2293 * @test
2294 */
2295 public function hmacReturnsEqualHashesForEqualInput()
2296 {
2297 $msg0 = 'message';
2298 $msg1 = 'message';
2299 $this->assertEquals(GeneralUtility::hmac($msg0), GeneralUtility::hmac($msg1));
2300 }
2301
2302 /**
2303 * @test
2304 */
2305 public function hmacReturnsNoEqualHashesForNonEqualInput()
2306 {
2307 $msg0 = 'message0';
2308 $msg1 = 'message1';
2309 $this->assertNotEquals(GeneralUtility::hmac($msg0), GeneralUtility::hmac($msg1));
2310 }
2311
2312 //////////////////////////////////
2313 // Tests concerning quoteJSvalue
2314 //////////////////////////////////
2315 /**
2316 * Data provider for quoteJSvalueTest.
2317 *
2318 * @return array
2319 */
2320 public function quoteJsValueDataProvider()
2321 {
2322 return [
2323 'Immune characters are returned as is' => [
2324 '._,',
2325 '._,'
2326 ],
2327 'Alphanumerical characters are returned as is' => [
2328 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
2329 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
2330 ],
2331 'Angle brackets and ampersand are encoded' => [
2332 '<>&',
2333 '\\u003C\\u003E\\u0026'
2334 ],
2335 'Quotes and backslashes are encoded' => [
2336 '"\'\\',
2337 '\\u0022\\u0027\\u005C'
2338 ],
2339 'Forward slashes are escaped' => [
2340 '</script>',
2341 '\\u003C\\/script\\u003E'
2342 ],
2343 'Empty string stays empty' => [
2344 '',
2345 ''
2346 ],
2347 'Exclamation mark and space are properly encoded' => [
2348 'Hello World!',
2349 'Hello\\u0020World\\u0021'
2350 ],
2351 'Whitespaces are properly encoded' => [
2352 "\t" . LF . CR . ' ',
2353 '\\u0009\\u000A\\u000D\\u0020'
2354 ],
2355 'Null byte is properly encoded' => [
2356 "\0",
2357 '\\u0000'
2358 ],
2359 'Umlauts are properly encoded' => [
2360 'ÜüÖöÄä',
2361 '\\u00dc\\u00fc\\u00d6\\u00f6\\u00c4\\u00e4'
2362 ]
2363 ];
2364 }
2365
2366 /**
2367 * @test
2368 * @param string $input
2369 * @param string $expected
2370 * @dataProvider quoteJsValueDataProvider
2371 */
2372 public function quoteJsValueTest($input, $expected)
2373 {
2374 $this->assertSame('\'' . $expected . '\'', GeneralUtility::quoteJSvalue($input));
2375 }
2376
2377 ///////////////////////////////
2378 // Tests concerning _GETset()
2379 ///////////////////////////////
2380 /**
2381 * @test
2382 */
2383 public function getSetWritesArrayToGetSystemVariable()
2384 {
2385 $_GET = [];
2386 $GLOBALS['HTTP_GET_VARS'] = [];
2387 $getParameters = ['foo' => 'bar'];
2388 GeneralUtility::_GETset($getParameters);
2389 $this->assertSame($getParameters, $_GET);
2390 }
2391
2392 /**
2393 * @test
2394 */
2395 public function getSetWritesArrayToGlobalsHttpGetVars()
2396 {
2397 $_GET = [];
2398 $GLOBALS['HTTP_GET_VARS'] = [];
2399 $getParameters = ['foo' => 'bar'];
2400 GeneralUtility::_GETset($getParameters);
2401 $this->assertSame($getParameters, $GLOBALS['HTTP_GET_VARS']);
2402 }
2403
2404 /**
2405 * @test
2406 */
2407 public function getSetForArrayDropsExistingValues()
2408 {
2409 $_GET = [];
2410 $GLOBALS['HTTP_GET_VARS'] = [];
2411 GeneralUtility::_GETset(['foo' => 'bar']);
2412 GeneralUtility::_GETset(['oneKey' => 'oneValue']);
2413 $this->assertEquals(['oneKey' => 'oneValue'], $GLOBALS['HTTP_GET_VARS']);
2414 }
2415
2416 /**
2417 * @test
2418 */
2419 public function getSetAssignsOneValueToOneKey()
2420 {
2421 $_GET = [];
2422 $GLOBALS['HTTP_GET_VARS'] = [];
2423 GeneralUtility::_GETset('oneValue', 'oneKey');
2424 $this->assertEquals('oneValue', $GLOBALS['HTTP_GET_VARS']['oneKey']);
2425 }
2426
2427 /**
2428 * @test
2429 */
2430 public function getSetForOneValueDoesNotDropUnrelatedValues()
2431 {
2432 $_GET = [];
2433 $GLOBALS['HTTP_GET_VARS'] = [];
2434 GeneralUtility::_GETset(['foo' => 'bar']);
2435 GeneralUtility::_GETset('oneValue', 'oneKey');
2436 $this->assertEquals(['foo' => 'bar', 'oneKey' => 'oneValue'], $GLOBALS['HTTP_GET_VARS']);
2437 }
2438
2439 /**
2440 * @test
2441 */
2442 public function getSetCanAssignsAnArrayToASpecificArrayElement()
2443 {
2444 $_GET = [];
2445 $GLOBALS['HTTP_GET_VARS'] = [];
2446 GeneralUtility::_GETset(['childKey' => 'oneValue'], 'parentKey');
2447 $this->assertEquals(['parentKey' => ['childKey' => 'oneValue']], $GLOBALS['HTTP_GET_VARS']);
2448 }
2449
2450 /**
2451 * @test
2452 */
2453 public function getSetCanAssignAStringValueToASpecificArrayChildElement()
2454 {
2455 $_GET = [];
2456 $GLOBALS['HTTP_GET_VARS'] = [];
2457 GeneralUtility::_GETset('oneValue', 'parentKey|childKey');
2458 $this->assertEquals(['parentKey' => ['childKey' => 'oneValue']], $GLOBALS['HTTP_GET_VARS']);
2459 }
2460
2461 /**
2462 * @test
2463 */
2464 public function getSetCanAssignAnArrayToASpecificArrayChildElement()
2465 {
2466 $_GET = [];
2467 $GLOBALS['HTTP_GET_VARS'] = [];
2468 GeneralUtility::_GETset(['key1' => 'value1', 'key2' => 'value2'], 'parentKey|childKey');
2469 $this->assertEquals([
2470 'parentKey' => [
2471 'childKey' => ['key1' => 'value1', 'key2' => 'value2']
2472 ]
2473 ], $GLOBALS['HTTP_GET_VARS']);
2474 }
2475
2476 ///////////////////////////
2477 // Tests concerning minifyJavaScript
2478 ///////////////////////////
2479 /**
2480 * @test
2481 */
2482 public function minifyJavaScriptReturnsInputStringIfNoHookIsRegistered()
2483 {
2484 unset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['minifyJavaScript']);
2485 $testString = $this->getUniqueId('string');
2486 $this->assertSame($testString, GeneralUtility::minifyJavaScript($testString));
2487 }
2488
2489 ///////////////////////////////
2490 // Tests concerning fixPermissions
2491 ///////////////////////////////
2492 /**
2493 * @test
2494 * @requires function posix_getegid
2495 */
2496 public function fixPermissionsSetsGroup()
2497 {
2498 if (Environment::isWindows()) {
2499 $this->markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2500 }
2501 if (posix_getegid() === -1) {
2502 $this->markTestSkipped('The fixPermissionsSetsGroup() is not available on Mac OS because posix_getegid() always returns -1 on Mac OS.');
2503 }
2504 // Create and prepare test file
2505 $filename = $this->getVirtualTestDir() . '/' . $this->getUniqueId('test_');
2506 GeneralUtilityFilesystemFixture::writeFileToTypo3tempDir($filename, '42');
2507 $currentGroupId = posix_getegid();
2508 // Set target group and run method
2509 $GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup'] = $currentGroupId;
2510 GeneralUtilityFilesystemFixture::fixPermissions($filename);
2511 clearstatcache();
2512 $this->assertEquals($currentGroupId, filegroup($filename));
2513 }
2514
2515 /**
2516 * @test
2517 */
2518 public function fixPermissionsSetsPermissionsToFile()
2519 {
2520 if (Environment::isWindows()) {
2521 $this->markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2522 }
2523 // Create and prepare test file
2524 $filename = $this->getVirtualTestDir() . '/' . $this->getUniqueId('test_');
2525 GeneralUtilityFilesystemFixture::writeFileToTypo3tempDir($filename, '42');
2526 chmod($filename, 482);
2527 // Set target permissions and run method
2528 $GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'] = '0660';
2529 $fixPermissionsResult = GeneralUtilityFilesystemFixture::fixPermissions($filename);
2530 clearstatcache();
2531 $this->assertTrue($fixPermissionsResult);
2532 $this->assertEquals('0660', substr(decoct(fileperms($filename)), 2));
2533 }
2534
2535 /**
2536 * @test
2537 */
2538 public function fixPermissionsSetsPermissionsToHiddenFile()
2539 {
2540 if (Environment::isWindows()) {
2541 $this->markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2542 }
2543 // Create and prepare test file
2544 $filename = $this->getVirtualTestDir() . '/' . $this->getUniqueId('test_');
2545 GeneralUtilityFilesystemFixture::writeFileToTypo3tempDir($filename, '42');
2546 chmod($filename, 482);
2547 // Set target permissions and run method
2548 $GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'] = '0660';
2549 $fixPermissionsResult = GeneralUtilityFilesystemFixture::fixPermissions($filename);
2550 clearstatcache();
2551 $this->assertTrue($fixPermissionsResult);
2552 $this->assertEquals('0660', substr(decoct(fileperms($filename)), 2));
2553 }
2554
2555 /**
2556 * @test
2557 */
2558 public function fixPermissionsSetsPermissionsToDirectory()
2559 {
2560 if (Environment::isWindows()) {
2561 $this->markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2562 }
2563 // Create and prepare test directory
2564 $directory = $this->getVirtualTestDir() . '/' . $this->getUniqueId('test_');
2565 GeneralUtilityFilesystemFixture::mkdir($directory);
2566 chmod($directory, 1551);
2567 // Set target permissions and run method
2568 $GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] = '0770';
2569 $fixPermissionsResult = GeneralUtilityFilesystemFixture::fixPermissions($directory);
2570 clearstatcache();
2571 $this->assertTrue($fixPermissionsResult);
2572 $this->assertEquals('0770', substr(decoct(fileperms($directory)), 1));
2573 }
2574
2575 /**
2576 * @test
2577 */
2578 public function fixPermissionsSetsPermissionsToDirectoryWithTrailingSlash()
2579 {
2580 if (Environment::isWindows()) {
2581 $this->markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2582 }
2583 // Create and prepare test directory
2584 $directory = $this->getVirtualTestDir() . '/' . $this->getUniqueId('test_');
2585 GeneralUtilityFilesystemFixture::mkdir($directory);
2586 chmod($directory, 1551);
2587 // Set target permissions and run method
2588 $GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] = '0770';
2589 $fixPermissionsResult = GeneralUtilityFilesystemFixture::fixPermissions($directory . '/');
2590 // Get actual permissions and clean up
2591 clearstatcache();
2592 $this->assertTrue($fixPermissionsResult);
2593 $this->assertEquals('0770', substr(decoct(fileperms($directory)), 1));
2594 }
2595
2596 /**
2597 * @test
2598 */
2599 public function fixPermissionsSetsPermissionsToHiddenDirectory()
2600 {
2601 if (Environment::isWindows()) {
2602 $this->markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2603 }
2604 // Create and prepare test directory
2605 $directory = $this->getVirtualTestDir() . '/' . $this->getUniqueId('test_');
2606 GeneralUtilityFilesystemFixture::mkdir($directory);
2607 chmod($directory, 1551);
2608 // Set target permissions and run method
2609 $GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] = '0770';
2610 $fixPermissionsResult = GeneralUtilityFilesystemFixture::fixPermissions($directory);
2611 // Get actual permissions and clean up
2612 clearstatcache();
2613 $this->assertTrue($fixPermissionsResult);
2614 $this->assertEquals('0770', substr(decoct(fileperms($directory)), 1));
2615 }
2616
2617 /**
2618 * @test
2619 */
2620 public function fixPermissionsCorrectlySetsPermissionsRecursive()
2621 {
2622 if (Environment::isWindows()) {
2623 $this->markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2624 }
2625 // Create and prepare test directory and file structure
2626 $baseDirectory = $this->getVirtualTestDir() . '/' . $this->getUniqueId('test_');
2627 GeneralUtilityFilesystemFixture::mkdir($baseDirectory);
2628 chmod($baseDirectory, 1751);
2629 GeneralUtilityFilesystemFixture::writeFileToTypo3tempDir($baseDirectory . '/file', '42');
2630 chmod($baseDirectory . '/file', 482);
2631 GeneralUtilityFilesystemFixture::mkdir($baseDirectory . '/foo');
2632 chmod($baseDirectory . '/foo', 1751);
2633 GeneralUtilityFilesystemFixture::writeFileToTypo3tempDir($baseDirectory . '/foo/file', '42');
2634 chmod($baseDirectory . '/foo/file', 482);
2635 GeneralUtilityFilesystemFixture::mkdir($baseDirectory . '/.bar');
2636 chmod($baseDirectory . '/.bar', 1751);
2637 // Use this if writeFileToTypo3tempDir is fixed to create hidden files in subdirectories
2638 // \TYPO3\CMS\Core\Utility\GeneralUtility::writeFileToTypo3tempDir($baseDirectory . '/.bar/.file', '42');
2639 // \TYPO3\CMS\Core\Utility\GeneralUtility::writeFileToTypo3tempDir($baseDirectory . '/.bar/..file2', '42');
2640 touch($baseDirectory . '/.bar/.file', '42');
2641 chmod($baseDirectory . '/.bar/.file', 482);
2642 touch($baseDirectory . '/.bar/..file2', '42');
2643 chmod($baseDirectory . '/.bar/..file2', 482);
2644 // Set target permissions and run method
2645 $GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'] = '0660';
2646 $GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] = '0770';
2647 $fixPermissionsResult = GeneralUtilityFilesystemFixture::fixPermissions($baseDirectory, true);
2648 // Get actual permissions
2649 clearstatcache();
2650 $resultBaseDirectoryPermissions = substr(decoct(fileperms($baseDirectory)), 1);
2651 $resultBaseFilePermissions = substr(decoct(fileperms($baseDirectory . '/file')), 2);
2652 $resultFooDirectoryPermissions = substr(decoct(fileperms($baseDirectory . '/foo')), 1);
2653 $resultFooFilePermissions = substr(decoct(fileperms($baseDirectory . '/foo/file')), 2);
2654 $resultBarDirectoryPermissions = substr(decoct(fileperms($baseDirectory . '/.bar')), 1);
2655 $resultBarFilePermissions = substr(decoct(fileperms($baseDirectory . '/.bar/.file')), 2);
2656 $resultBarFile2Permissions = substr(decoct(fileperms($baseDirectory . '/.bar/..file2')), 2);
2657 // Test if everything was ok
2658 $this->assertTrue($fixPermissionsResult);
2659 $this->assertEquals('0770', $resultBaseDirectoryPermissions);
2660 $this->assertEquals('0660', $resultBaseFilePermissions);
2661 $this->assertEquals('0770', $resultFooDirectoryPermissions);
2662 $this->assertEquals('0660', $resultFooFilePermissions);
2663 $this->assertEquals('0770', $resultBarDirectoryPermissions);
2664 $this->assertEquals('0660', $resultBarFilePermissions);
2665 $this->assertEquals('0660', $resultBarFile2Permissions);
2666 }
2667
2668 /**
2669 * @test
2670 */
2671 public function fixPermissionsDoesNotSetPermissionsToNotAllowedPath()
2672 {
2673 if (Environment::isWindows()) {
2674 $this->markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2675 }
2676 // Create and prepare test file
2677 $filename = Environment::getVarPath() . '/tests/../../../typo3temp/var/tests/' . $this->getUniqueId('test_');
2678 // Set target permissions and run method
2679 $GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'] = '0660';
2680 $fixPermissionsResult = GeneralUtility::fixPermissions($filename);
2681 $this->assertFalse($fixPermissionsResult);
2682 }
2683
2684 /**
2685 * @test
2686 */
2687 public function fixPermissionsSetsPermissionsWithRelativeFileReference()
2688 {
2689 if (Environment::isWindows()) {
2690 $this->markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2691 }
2692 $filename = 'typo3temp/var/tests/' . $this->getUniqueId('test_');
2693 GeneralUtility::writeFileToTypo3tempDir(Environment::getPublicPath() . '/' . $filename, '42');
2694 $this->testFilesToDelete[] = Environment::getPublicPath() . '/' . $filename;
2695 chmod(Environment::getPublicPath() . '/' . $filename, 482);
2696 // Set target permissions and run method
2697 $GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'] = '0660';
2698 $fixPermissionsResult = GeneralUtility::fixPermissions($filename);
2699 clearstatcache();
2700 $this->assertTrue($fixPermissionsResult);
2701 $this->assertEquals('0660', substr(decoct(fileperms(Environment::getPublicPath() . '/' . $filename)), 2));
2702 }
2703
2704 /**
2705 * @test
2706 */
2707 public function fixPermissionsSetsDefaultPermissionsToFile()
2708 {
2709 if (Environment::isWindows()) {
2710 $this->markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2711 }
2712 $filename = $this->getVirtualTestDir() . '/' . $this->getUniqueId('test_');
2713 GeneralUtilityFilesystemFixture::writeFileToTypo3tempDir($filename, '42');
2714 chmod($filename, 482);
2715 unset($GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask']);
2716 $fixPermissionsResult = GeneralUtilityFilesystemFixture::fixPermissions($filename);
2717 clearstatcache();
2718 $this->assertTrue($fixPermissionsResult);
2719 $this->assertEquals('0644', substr(decoct(fileperms($filename)), 2));
2720 }
2721
2722 /**
2723 * @test
2724 */
2725 public function fixPermissionsSetsDefaultPermissionsToDirectory()
2726 {
2727 if (Environment::isWindows()) {
2728 $this->markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2729 }
2730 $directory = $this->getVirtualTestDir() . '/' . $this->getUniqueId('test_');
2731 GeneralUtilityFilesystemFixture::mkdir($directory);
2732 chmod($directory, 1551);
2733 unset($GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask']);
2734 $fixPermissionsResult = GeneralUtilityFilesystemFixture::fixPermissions($directory);
2735 clearstatcache();
2736 $this->assertTrue($fixPermissionsResult);
2737 $this->assertEquals('0755', substr(decoct(fileperms($directory)), 1));
2738 }
2739
2740 ///////////////////////////////
2741 // Tests concerning mkdir
2742 ///////////////////////////////
2743 /**
2744 * @test
2745 */
2746 public function mkdirCreatesDirectory()
2747 {
2748 $directory = $this->getVirtualTestDir() . '/' . $this->getUniqueId('test_');
2749 $mkdirResult = GeneralUtilityFilesystemFixture::mkdir($directory);
2750 clearstatcache();
2751 $this->assertTrue($mkdirResult);
2752 $this->assertTrue(is_dir($directory));
2753 }
2754
2755 /**
2756 * @test
2757 */
2758 public function mkdirCreatesHiddenDirectory()
2759 {
2760 $directory = $this->getVirtualTestDir() . '/' . $this->getUniqueId('.test_');
2761 $mkdirResult = GeneralUtilityFilesystemFixture::mkdir($directory);
2762 clearstatcache();
2763 $this->assertTrue($mkdirResult);
2764 $this->assertTrue(is_dir($directory));
2765 }
2766
2767 /**
2768 * @test
2769 */
2770 public function mkdirCreatesDirectoryWithTrailingSlash()
2771 {
2772 $directory = $this->getVirtualTestDir() . '/' . $this->getUniqueId('test_') . '/';
2773 $mkdirResult = GeneralUtilityFilesystemFixture::mkdir($directory);
2774 clearstatcache();
2775 $this->assertTrue($mkdirResult);
2776 $this->assertTrue(is_dir($directory));
2777 }
2778
2779 /**
2780 * @test
2781 */
2782 public function mkdirSetsPermissionsOfCreatedDirectory()
2783 {
2784 if (Environment::isWindows()) {
2785 $this->markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2786 }
2787 $directory = $this->getVirtualTestDir() . '/' . $this->getUniqueId('test_');
2788 $oldUmask = umask(19);
2789 $GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] = '0772';
2790 GeneralUtilityFilesystemFixture::mkdir($directory);
2791 clearstatcache();
2792 $resultDirectoryPermissions = substr(decoct(fileperms($directory)), 1);
2793 umask($oldUmask);
2794 $this->assertEquals($resultDirectoryPermissions, '0772');
2795 }
2796
2797 /**
2798 * @test
2799 */
2800 public function mkdirSetsGroupOwnershipOfCreatedDirectory()
2801 {
2802 $swapGroup = $this->checkGroups(__FUNCTION__);
2803 if ($swapGroup !== false) {
2804 $GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup'] = $swapGroup;
2805 $directory = $this->getVirtualTestDir() . '/' . $this->getUniqueId('mkdirtest_');
2806 GeneralUtilityFilesystemFixture::mkdir($directory);
2807 clearstatcache();
2808 $resultDirectoryGroup = filegroup($directory);
2809 $this->assertEquals($resultDirectoryGroup, $swapGroup);
2810 }
2811 }
2812
2813 ///////////////////////////////
2814 // Helper function for filesystem ownership tests
2815 ///////////////////////////////
2816 /**
2817 * Check if test on filesystem group ownership can be done in this environment
2818 * If so, return second group of webserver user
2819 *
2820 * @param string $methodName calling method name
2821 * @return mixed FALSE if test cannot be run, int group id of the second group of webserver user
2822 * @requires function posix_getegid
2823 * @requires function posix_getgroups
2824 */
2825 private function checkGroups($methodName)
2826 {
2827 if (Environment::isWindows()) {
2828 $this->markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2829 return false;
2830 }
2831 if (posix_getegid() === -1) {
2832 $this->markTestSkipped('Function posix_getegid() returns -1, ' . $methodName . '() tests skipped');
2833 return false;
2834 }
2835 $groups = posix_getgroups();
2836 if (count($groups) <= 1) {
2837 $this->markTestSkipped($methodName . '() test cannot be done when the web server user is only member of 1 group.');
2838 return false;
2839 }
2840 $secondaryGroups = array_diff($groups, [posix_getegid()]);
2841 return array_shift($secondaryGroups);
2842 }
2843
2844 /////////////////////////////////////////////
2845 // Tests concerning writeFileToTypo3tempDir()
2846 /////////////////////////////////////////////
2847
2848 /**
2849 * @return array
2850 */
2851 public function invalidFilePathForTypo3tempDirDataProvider()
2852 {
2853 return [
2854 [
2855 Environment::getPublicPath() . '/../path/this-path-has-more-than-60-characters-in-one-base-path-you-can-even-count-more',
2856 'Input filepath "' . Environment::getPublicPath() . '/../path/this-path-has-more-than-60-characters-in-one-base-path-you-can-even-count-more" was generally invalid!'
2857 ],
2858 [
2859 Environment::getPublicPath() . '/dummy/path/this-path-has-more-than-60-characters-in-one-base-path-you-can-even-count-more',
2860 'Input filepath "' . Environment::getPublicPath() . '/dummy/path/this-path-has-more-than-60-characters-in-one-base-path-you-can-even-count-more" was generally invalid!'
2861 ],
2862 [
2863 Environment::getPublicPath() . '/dummy/path/this-path-has-more-than-60-characters-in-one-base-path-you-can-even-count-more',
2864 'Input filepath "' . Environment::getPublicPath() . '/dummy/path/this-path-has-more-than-60-characters-in-one-base-path-you-can-even-count-more" was generally invalid!'
2865 ],
2866 [
2867 '/dummy/path/awesome',
2868 '"/dummy/path/" was not within directory Environment::getPublicPath() + "/typo3temp/"'
2869 ],
2870 [
2871 Environment::getPublicPath() . '/typo3conf/path',
2872 '"' . Environment::getPublicPath() . '/typo3conf/" was not within directory Environment::getPublicPath() + "/typo3temp/"',
2873 ],
2874 [
2875 Environment::getPublicPath() . '/typo3temp/táylor/swíft',
2876 'Subdir, "táylor/", was NOT on the form "[[:alnum:]_]/+"',
2877 ],
2878 'Path instead of file given' => [
2879 Environment::getPublicPath() . '/typo3temp/dummy/path/',
2880 'Calculated file location didn\'t match input "' . Environment::getPublicPath() . '/typo3temp/dummy/path/".'
2881 ],
2882 ];
2883 }
2884
2885 /**
2886 * @test
2887 * @dataProvider invalidFilePathForTypo3tempDirDataProvider
2888 * @param string $invalidFilePath
2889 * @param string $expectedResult
2890 */
2891 public function writeFileToTypo3tempDirFailsWithInvalidPath($invalidFilePath, string $expectedResult)
2892 {
2893 $result = GeneralUtility::writeFileToTypo3tempDir($invalidFilePath, 'dummy content to be written');
2894 $this->assertSame($result, $expectedResult);
2895 }
2896
2897 /**
2898 * @return array
2899 */
2900 public function validFilePathForTypo3tempDirDataProvider()
2901 {
2902 return [
2903 'Default text file' => [
2904 Environment::getPublicPath() . '/typo3temp/var/paranoid/android.txt',
2905 ],
2906 'Html file extension' => [
2907 Environment::getPublicPath() . '/typo3temp/var/karma.html',
2908 ],
2909 'No file extension' => [
2910 Environment::getPublicPath() . '/typo3temp/var/no-surprises',
2911 ],
2912 'Deep directory' => [
2913 Environment::getPublicPath() . '/typo3temp/var/climbing/up/the/walls',
2914 ],
2915 ];
2916 }
2917
2918 /**
2919 * @test
2920 * @dataProvider validFilePathForTypo3tempDirDataProvider
2921 * @param string $filePath
2922 */
2923 public function writeFileToTypo3tempDirWorksWithValidPath($filePath)
2924 {
2925 $dummyContent = 'Please could you stop the noise, I\'m trying to get some rest from all the unborn chicken voices in my head.';
2926
2927 $this->testFilesToDelete[] = $filePath;
2928
2929 $result = GeneralUtility::writeFileToTypo3tempDir($filePath, $dummyContent);
2930
2931 $this->assertNull($result);
2932 $this->assertFileExists($filePath);
2933 $this->assertStringEqualsFile($filePath, $dummyContent);
2934 }
2935
2936 ///////////////////////////////
2937 // Tests concerning mkdir_deep
2938 ///////////////////////////////
2939 /**
2940 * @test
2941 */
2942 public function mkdirDeepCreatesDirectory()
2943 {
2944 $directory = $this->getVirtualTestDir() . '/' . $this->getUniqueId('test_');
2945 GeneralUtility::mkdir_deep($directory);
2946 $this->assertTrue(is_dir($directory));
2947 }
2948
2949 /**
2950 * @test
2951 */
2952 public function mkdirDeepCreatesSubdirectoriesRecursive()
2953 {
2954 $directory = $this->getVirtualTestDir() . 'typo3temp/var/tests/' . $this->getUniqueId('test_');
2955 $subDirectory = $directory . '/foo';
2956 GeneralUtility::mkdir_deep($subDirectory);
2957 $this->assertTrue(is_dir($subDirectory));
2958 }
2959
2960 /**
2961 * Data provider for mkdirDeepCreatesDirectoryWithDoubleSlashes.
2962 * @return array
2963 */
2964 public function mkdirDeepCreatesDirectoryWithAndWithoutDoubleSlashesDataProvider()
2965 {
2966 return [
2967 'no double slash if concatenated with Environment::getPublicPath()' => ['fileadmin/testDir1'],
2968 'double slash if concatenated with Environment::getPublicPath()' => ['/fileadmin/testDir2'],
2969 ];
2970 }
2971
2972 /**
2973 * @test
2974 * @dataProvider mkdirDeepCreatesDirectoryWithAndWithoutDoubleSlashesDataProvider
2975 */
2976 public function mkdirDeepCreatesDirectoryWithDoubleSlashes($directoryToCreate)
2977 {
2978 vfsStream::setup();
2979 // Load fixture files and folders from disk
2980 FileStreamWrapper::init(Environment::getPublicPath());
2981 FileStreamWrapper::registerOverlayPath('fileadmin', 'vfs://root/fileadmin', true);
2982 GeneralUtility::mkdir_deep(Environment::getPublicPath() . '/' . $directoryToCreate);
2983 $this->assertTrue(is_dir(Environment::getPublicPath() . '/' . $directoryToCreate));
2984 FileStreamWrapper::destroy();
2985 }
2986
2987 /**
2988 * @test
2989 */
2990 public function mkdirDeepFixesPermissionsOfCreatedDirectory()
2991 {
2992 if (Environment::isWindows()) {
2993 $this->markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2994 }
2995 $directory = $this->getUniqueId('mkdirdeeptest_');
2996 $oldUmask = umask(19);
2997 $GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] = '0777';
2998 GeneralUtility::mkdir_deep(Environment::getVarPath() . '/tests/' . $directory);
2999 $this->testFilesToDelete[] = Environment::getVarPath() . '/tests/' . $directory;
3000 clearstatcache();
3001 umask($oldUmask);
3002 $this->assertEquals('777', substr(decoct(fileperms(Environment::getVarPath() . '/tests/' . $directory)), -3, 3));
3003 }
3004
3005 /**
3006 * @test
3007 */
3008 public function mkdirDeepFixesPermissionsOnNewParentDirectory()
3009 {
3010 if (Environment::isWindows()) {
3011 $this->markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
3012 }
3013 $directory = $this->getUniqueId('mkdirdeeptest_');
3014 $subDirectory = $directory . '/bar';
3015 $GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] = '0777';
3016 $oldUmask = umask(19);
3017 GeneralUtility::mkdir_deep(Environment::getVarPath() . '/tests/' . $subDirectory);
3018 $this->testFilesToDelete[] = Environment::getVarPath() . '/tests/' . $directory;
3019 clearstatcache();
3020 umask($oldUmask);
3021 $this->assertEquals('777', substr(decoct(fileperms(Environment::getVarPath() . '/tests/' . $directory)), -3, 3));
3022 }
3023
3024 /**
3025 * @test
3026 */
3027 public function mkdirDeepDoesNotChangePermissionsOfExistingSubDirectories()
3028 {
3029 if (Environment::isWindows()) {
3030 $this->markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
3031 }
3032 $baseDirectory = Environment::getVarPath() . '/tests/';
3033 $existingDirectory = $this->getUniqueId('test_existing_') . '/';
3034 $newSubDirectory = $this->getUniqueId('test_new_');
3035 @mkdir($baseDirectory . $existingDirectory);
3036 $this->testFilesToDelete[] = $baseDirectory . $existingDirectory;
3037 chmod($baseDirectory . $existingDirectory, 482);
3038 GeneralUtility::mkdir_deep($baseDirectory . $existingDirectory . $newSubDirectory);
3039 $this->assertEquals(742, (int)substr(decoct(fileperms($baseDirectory . $existingDirectory)), 2));
3040 }
3041
3042 /**
3043 * @test
3044 */
3045 public function mkdirDeepSetsGroupOwnershipOfCreatedDirectory()
3046 {
3047 $swapGroup = $this->checkGroups(__FUNCTION__);
3048 if ($swapGroup !== false) {
3049 $GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup'] = $swapGroup;
3050 $directory = $this->getUniqueId('mkdirdeeptest_');
3051 GeneralUtility::mkdir_deep(Environment::getVarPath() . '/tests/' . $directory);
3052 $this->testFilesToDelete[] = Environment::getVarPath() . '/tests/' . $directory;
3053 clearstatcache();
3054 $resultDirectoryGroup = filegroup(Environment::getVarPath() . '/tests/' . $directory);
3055 $this->assertEquals($resultDirectoryGroup, $swapGroup);
3056 }
3057 }
3058
3059 /**
3060 * @test
3061 */
3062 public function mkdirDeepSetsGroupOwnershipOfCreatedParentDirectory()
3063 {
3064 $swapGroup = $this->checkGroups(__FUNCTION__);
3065 if ($swapGroup !== false) {
3066 $GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup'] = $swapGroup;
3067 $directory = $this->getUniqueId('mkdirdeeptest_');
3068 $subDirectory = $directory . '/bar';
3069 GeneralUtility::mkdir_deep(Environment::getVarPath() . '/tests/' . $subDirectory);
3070 $this->testFilesToDelete[] = Environment::getVarPath() . '/tests/' . $directory;
3071 clearstatcache();
3072 $resultDirectoryGroup = filegroup(Environment::getVarPath() . '/tests/' . $directory);
3073 $this->assertEquals($resultDirectoryGroup, $swapGroup);
3074 }
3075 }
3076
3077 /**
3078 * @test
3079 */
3080 public function mkdirDeepSetsGroupOwnershipOnNewSubDirectory()
3081 {
3082 $swapGroup = $this->checkGroups(__FUNCTION__);
3083 if ($swapGroup !== false) {
3084 $GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup'] = $swapGroup;
3085 $directory = $this->getUniqueId('mkdirdeeptest_');
3086 $subDirectory = $directory . '/bar';
3087 GeneralUtility::mkdir_deep(Environment::getVarPath() . '/tests/' . $subDirectory);
3088 $this->testFilesToDelete[] = Environment::getVarPath() . '/tests/' . $directory;
3089 clearstatcache();
3090 $resultDirectoryGroup = filegroup(Environment::getVarPath() . '/tests/' . $directory);
3091 $this->assertEquals($resultDirectoryGroup, $swapGroup);
3092 }
3093 }
3094
3095 /**
3096 * @test
3097 */
3098 public function mkdirDeepCreatesDirectoryInVfsStream()
3099 {
3100 if (!class_exists('org\\bovigo\\vfs\\vfsStreamWrapper')) {
3101 $this->markTestSkipped('mkdirDeepCreatesDirectoryInVfsStream() test not available with this phpunit version.');
3102 }
3103 vfsStreamWrapper::register();
3104 $baseDirectory = $this->getUniqueId('test_');
3105 vfsStreamWrapper::setRoot(new vfsStreamDirectory($baseDirectory));
3106 GeneralUtility::mkdir_deep('vfs://' . $baseDirectory . '/sub');
3107 $this->assertTrue(is_dir('vfs://' . $baseDirectory . '/sub'));
3108 }
3109
3110 /**
3111 * @test
3112 */
3113 public function mkdirDeepThrowsExceptionIfDirectoryCreationFails()
3114 {
3115 $this->expectException(\RuntimeException::class);
3116 $this->expectExceptionCode(1170251401);
3117
3118 GeneralUtility::mkdir_deep('http://localhost');
3119 }
3120
3121 /**
3122 * @test
3123 */
3124 public function mkdirDeepThrowsExceptionIfBaseDirectoryIsNotOfTypeString()
3125 {
3126 $this->expectException(\InvalidArgumentException::class);
3127 $this->expectExceptionCode(1303662955);
3128
3129 GeneralUtility::mkdir_deep([]);
3130 }
3131
3132 /**
3133 * @test
3134 */
3135 public function mkdirDeepThrowsExceptionIfDeepDirectoryIsNotOfTypeString()
3136 {
3137 $this->expectException(\InvalidArgumentException::class);
3138 $this->expectExceptionCode(1303662956);
3139
3140 GeneralUtility