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