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