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