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