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