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