makeInstanceWithBeginningSlashInClassNameThrowsException()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
namespace TYPO3\CMS\Core\Tests\Unit\Utility;
19
20
use org\bovigo\vfs\vfsStream;
21
use org\bovigo\vfs\vfsStreamDirectory;
22
use org\bovigo\vfs\vfsStreamWrapper;
23
use Prophecy\Argument;
24
use Prophecy\PhpUnit\ProphecyTrait;
25
use Psr\Log\LoggerInterface;
26
use TYPO3\CMS\Core\Cache\CacheManager;
27
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
28
use TYPO3\CMS\Core\Core\Environment;
29
use TYPO3\CMS\Core\Package\Package;
30
use TYPO3\CMS\Core\Package\PackageManager;
31
use TYPO3\CMS\Core\SingletonInterface;
32
use TYPO3\CMS\Core\Tests\Unit\Utility\AccessibleProxies\ExtensionManagementUtilityAccessibleProxy;
33
use TYPO3\CMS\Core\Tests\Unit\Utility\Fixtures\ExtendedSingletonClassFixture;
34
use TYPO3\CMS\Core\Tests\Unit\Utility\Fixtures\GeneralUtilityFilesystemFixture;
35
use TYPO3\CMS\Core\Tests\Unit\Utility\Fixtures\GeneralUtilityFixture;
36
use TYPO3\CMS\Core\Tests\Unit\Utility\Fixtures\GeneralUtilityMakeInstanceInjectLoggerFixture;
37
use TYPO3\CMS\Core\Tests\Unit\Utility\Fixtures\OriginalClassFixture;
38
use TYPO3\CMS\Core\Tests\Unit\Utility\Fixtures\OtherReplacementClassFixture;
39
use TYPO3\CMS\Core\Tests\Unit\Utility\Fixtures\ReplacementClassFixture;
40
use TYPO3\CMS\Core\Tests\Unit\Utility\Fixtures\SingletonClassFixture;
41
use TYPO3\CMS\Core\Tests\Unit\Utility\Fixtures\TwoParametersConstructorFixture;
42
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
43
use TYPO3\CMS\Core\Utility\GeneralUtility;
44
use TYPO3\CMS\Core\Utility\StringUtility;
45
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
46
47
/**
48
 * Test case
49
 */
50
class GeneralUtilityTest extends UnitTestCase
51
{
52
    public const NO_FIX_PERMISSIONS_ON_WINDOWS = 'fixPermissions() not available on Windows (method does nothing)';
53
54
    use ProphecyTrait;
55
56
    /**
57
     * @var bool Reset singletons created by subject
58
     */
59
    protected bool $resetSingletonInstances = true;
60
61
    /**
62
     * @var bool Restore Environment after tests
63
     */
64
    protected bool $backupEnvironment = true;
65
66
    protected ?PackageManager $backupPackageManager;
67
68
    /**
69
     * Set up
70
     */
71
    protected function setUp(): void
72
    {
73
        parent::setUp();
74
        $this->backupPackageManager = ExtensionManagementUtilityAccessibleProxy::getPackageManager();
75
    }
76
77
    /**
78
     * Tear down
79
     */
80
    protected function tearDown(): void
81
    {
82
        GeneralUtility::flushInternalRuntimeCaches();
83
        if ($this->backupPackageManager) {
84
            ExtensionManagementUtilityAccessibleProxy::setPackageManager($this->backupPackageManager);
85
        }
86
        parent::tearDown();
87
    }
88
89
    /**
90
     * Helper method to test for an existing internet connection.
91
     * Some tests are skipped if there is no working uplink.
92
     *
93
     * @return bool $isConnected
94
     */
95
    public function isConnected(): bool
96
    {
97
        $isConnected = false;
98
        $connected = @fsockopen('typo3.org', 80);
99
        if ($connected) {
0 ignored issues
show
introduced by
$connected is of type false|resource, thus it always evaluated to false.
Loading history...
100
            $isConnected = true;
101
            fclose($connected);
102
        }
103
        return $isConnected;
104
    }
105
106
    /**
107
     * Helper method to create a random directory in the virtual file system
108
     * and return the path.
109
     *
110
     * @param string $prefix
111
     * @return string
112
     */
113
    protected function getVirtualTestDir(string $prefix = 'root_'): string
114
    {
115
        $root = vfsStream::setup();
116
        $path = $root->url() . '/typo3temp/var/tests/' . StringUtility::getUniqueId($prefix);
117
        GeneralUtility::mkdir_deep($path);
118
        return $path;
119
    }
120
121
    ///////////////////////////
122
    // Tests concerning _GP
123
    ///////////////////////////
124
    /**
125
     * @test
126
     * @dataProvider gpDataProvider
127
     */
128
    public function canRetrieveValueWithGP($key, $get, $post, $expected): void
129
    {
130
        $_GET = $get;
131
        $_POST = $post;
132
        self::assertSame($expected, GeneralUtility::_GP($key));
133
    }
134
135
    /**
136
     * Data provider for canRetrieveValueWithGP.
137
     * All test values also check whether slashes are stripped properly.
138
     *
139
     * @return array
140
     */
141
    public function gpDataProvider(): array
142
    {
143
        return [
144
            'No key parameter' => [null, [], [], null],
145
            'Key not found' => ['cake', [], [], null],
146
            'Value only in GET' => ['cake', ['cake' => 'li\\e'], [], 'li\\e'],
147
            'Value only in POST' => ['cake', [], ['cake' => 'l\\ie'], 'l\\ie'],
148
            'Value from POST preferred over GET' => ['cake', ['cake' => 'is a'], ['cake' => '\\lie'], '\\lie'],
149
            'Value can be an array' => [
150
                'cake',
151
                ['cake' => ['is a' => 'l\\ie']],
152
                [],
153
                ['is a' => 'l\\ie'],
154
            ],
155
            'Empty-ish key' => ['0', ['0' => 'zero'], ['0' => 'zero'], null],
156
        ];
157
    }
158
159
    ///////////////////////////
160
    // Tests concerning _GPmerged
161
    ///////////////////////////
162
    /**
163
     * @test
164
     * @dataProvider gpMergedDataProvider
165
     */
166
    public function gpMergedWillMergeArraysFromGetAndPost($get, $post, $expected): void
167
    {
168
        $_POST = $post;
169
        $_GET = $get;
170
        self::assertEquals($expected, GeneralUtility::_GPmerged('cake'));
171
    }
172
173
    /**
174
     * Data provider for gpMergedWillMergeArraysFromGetAndPost
175
     *
176
     * @return array
177
     */
178
    public function gpMergedDataProvider(): array
179
    {
180
        $fullDataArray = ['cake' => ['a' => 'is a', 'b' => 'lie']];
181
        $postPartData = ['cake' => ['b' => 'lie']];
182
        $getPartData = ['cake' => ['a' => 'is a']];
183
        $getPartDataModified = ['cake' => ['a' => 'is not a']];
184
        return [
185
            'Key doesn\' exist' => [['foo'], ['bar'], []],
186
            'No POST data' => [$fullDataArray, [], $fullDataArray['cake']],
187
            'No GET data' => [[], $fullDataArray, $fullDataArray['cake']],
188
            'POST and GET are merged' => [$getPartData, $postPartData, $fullDataArray['cake']],
189
            'POST is preferred over GET' => [$getPartDataModified, $fullDataArray, $fullDataArray['cake']],
190
        ];
191
    }
192
193
    ///////////////////////////////
194
    // Tests concerning _GET / _POST
195
    ///////////////////////////////
196
    /**
197
     * Data provider for canRetrieveGlobalInputsThroughGet
198
     * and canRetrieveGlobalInputsThroughPost
199
     *
200
     * @return array
201
     */
202
    public function getAndPostDataProvider(): array
203
    {
204
        return [
205
            'Requested input data doesn\'t exist' => ['cake', [], null],
206
            'No key will return entire input data' => [null, ['cake' => 'l\\ie'], ['cake' => 'l\\ie']],
207
            'Can retrieve specific input' => ['cake', ['cake' => 'l\\ie', 'foo'], 'l\\ie'],
208
            'Can retrieve nested input data' => ['cake', ['cake' => ['is a' => 'l\\ie']], ['is a' => 'l\\ie']],
209
        ];
210
    }
211
212
    /**
213
     * @test
214
     * @dataProvider getAndPostDataProvider
215
     */
216
    public function canRetrieveGlobalInputsThroughGet($key, $get, $expected): void
217
    {
218
        $_GET = $get;
219
        self::assertSame($expected, GeneralUtility::_GET($key));
220
    }
221
222
    /**
223
     * @test
224
     * @dataProvider getAndPostDataProvider
225
     */
226
    public function canRetrieveGlobalInputsThroughPost($key, $post, $expected): void
227
    {
228
        $_POST = $post;
229
        self::assertSame($expected, GeneralUtility::_POST($key));
230
    }
231
232
    ///////////////////////////
233
    // Tests concerning cmpIPv4
234
    ///////////////////////////
235
    /**
236
     * Data provider for cmpIPv4ReturnsTrueForMatchingAddress
237
     *
238
     * @return array Data sets
239
     */
240
    public static function cmpIPv4DataProviderMatching(): array
241
    {
242
        return [
243
            'host with full IP address' => ['127.0.0.1', '127.0.0.1'],
244
            'host with two wildcards at the end' => ['127.0.0.1', '127.0.*.*'],
245
            'host with wildcard at third octet' => ['127.0.0.1', '127.0.*.1'],
246
            'host with wildcard at second octet' => ['127.0.0.1', '127.*.0.1'],
247
            '/8 subnet' => ['127.0.0.1', '127.1.1.1/8'],
248
            '/32 subnet (match only name)' => ['127.0.0.1', '127.0.0.1/32'],
249
            '/30 subnet' => ['10.10.3.1', '10.10.3.3/30'],
250
            'host with wildcard in list with IPv4/IPv6 addresses' => ['192.168.1.1', '127.0.0.1, 1234:5678::/126, 192.168.*'],
251
            'host in list with IPv4/IPv6 addresses' => ['192.168.1.1', '::1, 1234:5678::/126, 192.168.1.1'],
252
        ];
253
    }
254
255
    /**
256
     * @test
257
     * @dataProvider cmpIPv4DataProviderMatching
258
     */
259
    public function cmpIPv4ReturnsTrueForMatchingAddress($ip, $list): void
260
    {
261
        self::assertTrue(GeneralUtility::cmpIPv4($ip, $list));
262
    }
263
264
    /**
265
     * Data provider for cmpIPv4ReturnsFalseForNotMatchingAddress
266
     *
267
     * @return array Data sets
268
     */
269
    public static function cmpIPv4DataProviderNotMatching(): array
270
    {
271
        return [
272
            'single host' => ['127.0.0.1', '127.0.0.2'],
273
            'single host with wildcard' => ['127.0.0.1', '127.*.1.1'],
274
            'single host with /32 subnet mask' => ['127.0.0.1', '127.0.0.2/32'],
275
            '/31 subnet' => ['127.0.0.1', '127.0.0.2/31'],
276
            'list with IPv4/IPv6 addresses' => ['127.0.0.1', '10.0.2.3, 192.168.1.1, ::1'],
277
            'list with only IPv6 addresses' => ['10.20.30.40', '::1, 1234:5678::/127'],
278
        ];
279
    }
280
281
    /**
282
     * @test
283
     * @dataProvider cmpIPv4DataProviderNotMatching
284
     */
285
    public function cmpIPv4ReturnsFalseForNotMatchingAddress($ip, $list): void
286
    {
287
        self::assertFalse(GeneralUtility::cmpIPv4($ip, $list));
288
    }
289
290
    ///////////////////////////
291
    // Tests concerning cmpIPv6
292
    ///////////////////////////
293
    /**
294
     * Data provider for cmpIPv6ReturnsTrueForMatchingAddress
295
     *
296
     * @return array Data sets
297
     */
298
    public static function cmpIPv6DataProviderMatching(): array
299
    {
300
        return [
301
            'empty address' => ['::', '::'],
302
            'empty with netmask in list' => ['::', '::/0'],
303
            'empty with netmask 0 and host-bits set in list' => ['::', '::123/0'],
304
            'localhost' => ['::1', '::1'],
305
            'localhost with leading zero blocks' => ['::1', '0:0::1'],
306
            'host with submask /128' => ['::1', '0:0::1/128'],
307
            '/16 subnet' => ['1234::1', '1234:5678::/16'],
308
            '/126 subnet' => ['1234:5678::3', '1234:5678::/126'],
309
            '/126 subnet with host-bits in list set' => ['1234:5678::3', '1234:5678::2/126'],
310
            'list with IPv4/IPv6 addresses' => ['1234:5678::3', '::1, 127.0.0.1, 1234:5678::/126, 192.168.1.1'],
311
        ];
312
    }
313
314
    /**
315
     * @test
316
     * @dataProvider cmpIPv6DataProviderMatching
317
     */
318
    public function cmpIPv6ReturnsTrueForMatchingAddress($ip, $list): void
319
    {
320
        self::assertTrue(GeneralUtility::cmpIPv6($ip, $list));
321
    }
322
323
    /**
324
     * Data provider for cmpIPv6ReturnsFalseForNotMatchingAddress
325
     *
326
     * @return array Data sets
327
     */
328
    public static function cmpIPv6DataProviderNotMatching(): array
329
    {
330
        return [
331
            'empty against localhost' => ['::', '::1'],
332
            'empty against localhost with /128 netmask' => ['::', '::1/128'],
333
            'localhost against different host' => ['::1', '::2'],
334
            'localhost against host with prior bits set' => ['::1', '::1:1'],
335
            'host against different /17 subnet' => ['1234::1', '1234:f678::/17'],
336
            'host against different /127 subnet' => ['1234:5678::3', '1234:5678::/127'],
337
            'host against IPv4 address list' => ['1234:5678::3', '127.0.0.1, 192.168.1.1'],
338
            'host against mixed list with IPv6 host in different subnet' => ['1234:5678::3', '::1, 1234:5678::/127'],
339
        ];
340
    }
341
342
    /**
343
     * @test
344
     * @dataProvider cmpIPv6DataProviderNotMatching
345
     */
346
    public function cmpIPv6ReturnsFalseForNotMatchingAddress($ip, $list): void
347
    {
348
        self::assertFalse(GeneralUtility::cmpIPv6($ip, $list));
349
    }
350
351
    /////////////////////////////////
352
    // Tests concerning normalizeIPv6
353
    /////////////////////////////////
354
    /**
355
     * Data provider for normalizeIPv6ReturnsCorrectlyNormalizedFormat
356
     *
357
     * @return array Data sets
358
     */
359
    public static function normalizeCompressIPv6DataProviderCorrect(): array
360
    {
361
        return [
362
            'empty' => ['::', '0000:0000:0000:0000:0000:0000:0000:0000'],
363
            'localhost' => ['::1', '0000:0000:0000:0000:0000:0000:0000:0001'],
364
            'expansion in middle 1' => ['1::2', '0001:0000:0000:0000:0000:0000:0000:0002'],
365
            'expansion in middle 2' => ['1:2::3', '0001:0002:0000:0000:0000:0000:0000:0003'],
366
            'expansion in middle 3' => ['1::2:3', '0001:0000:0000:0000:0000:0000:0002:0003'],
367
            'expansion in middle 4' => ['1:2::3:4:5', '0001:0002:0000:0000:0000:0003:0004:0005'],
368
        ];
369
    }
370
371
    /**
372
     * @test
373
     * @dataProvider normalizeCompressIPv6DataProviderCorrect
374
     */
375
    public function normalizeIPv6CorrectlyNormalizesAddresses($compressed, $normalized): void
376
    {
377
        self::assertEquals($normalized, GeneralUtility::normalizeIPv6($compressed));
378
    }
379
380
    ///////////////////////////////
381
    // Tests concerning validIP
382
    ///////////////////////////////
383
    /**
384
     * Data provider for checkValidIpReturnsTrueForValidIp
385
     *
386
     * @return array Data sets
387
     */
388
    public static function validIpDataProvider(): array
389
    {
390
        return [
391
            '0.0.0.0' => ['0.0.0.0'],
392
            'private IPv4 class C' => ['192.168.0.1'],
393
            'private IPv4 class A' => ['10.0.13.1'],
394
            'private IPv6' => ['fe80::daa2:5eff:fe8b:7dfb'],
395
        ];
396
    }
397
398
    /**
399
     * @test
400
     * @dataProvider validIpDataProvider
401
     */
402
    public function validIpReturnsTrueForValidIp($ip): void
403
    {
404
        self::assertTrue(GeneralUtility::validIP($ip));
405
    }
406
407
    /**
408
     * Data provider for checkValidIpReturnsFalseForInvalidIp
409
     *
410
     * @return array Data sets
411
     */
412
    public static function invalidIpDataProvider(): array
413
    {
414
        return [
415
            'null' => [null],
416
            'zero' => [0],
417
            'string' => ['test'],
418
            'string empty' => [''],
419
            'string NULL' => ['NULL'],
420
            'out of bounds IPv4' => ['300.300.300.300'],
421
            'dotted decimal notation with only two dots' => ['127.0.1'],
422
        ];
423
    }
424
425
    /**
426
     * @test
427
     * @dataProvider invalidIpDataProvider
428
     */
429
    public function validIpReturnsFalseForInvalidIp($ip): void
430
    {
431
        self::assertFalse(GeneralUtility::validIP($ip));
432
    }
433
434
    ///////////////////////////////
435
    // Tests concerning cmpFQDN
436
    ///////////////////////////////
437
    /**
438
     * Data provider for cmpFqdnReturnsTrue
439
     *
440
     * @return array Data sets
441
     */
442
    public static function cmpFqdnValidDataProvider(): array
443
    {
444
        return [
445
            'localhost should usually resolve, IPv4' => ['127.0.0.1', '*'],
446
            'localhost should usually resolve, IPv6' => ['::1', '*'],
447
            // other testcases with resolving not possible since it would
448
            // require a working IPv4/IPv6-connectivity
449
            'aaa.bbb.ccc.ddd.eee, full' => ['aaa.bbb.ccc.ddd.eee', 'aaa.bbb.ccc.ddd.eee'],
450
            'aaa.bbb.ccc.ddd.eee, wildcard first' => ['aaa.bbb.ccc.ddd.eee', '*.ccc.ddd.eee'],
451
            'aaa.bbb.ccc.ddd.eee, wildcard last' => ['aaa.bbb.ccc.ddd.eee', 'aaa.bbb.ccc.*'],
452
            'aaa.bbb.ccc.ddd.eee, wildcard middle' => ['aaa.bbb.ccc.ddd.eee', 'aaa.*.eee'],
453
            'list-matches, 1' => ['aaa.bbb.ccc.ddd.eee', 'xxx, yyy, zzz, aaa.*.eee'],
454
            'list-matches, 2' => ['aaa.bbb.ccc.ddd.eee', '127:0:0:1,,aaa.*.eee,::1'],
455
        ];
456
    }
457
458
    /**
459
     * @test
460
     * @dataProvider cmpFqdnValidDataProvider
461
     */
462
    public function cmpFqdnReturnsTrue($baseHost, $list): void
463
    {
464
        self::assertTrue(GeneralUtility::cmpFQDN($baseHost, $list));
465
    }
466
467
    /**
468
     * Data provider for cmpFqdnReturnsFalse
469
     *
470
     * @return array Data sets
471
     */
472
    public static function cmpFqdnInvalidDataProvider(): array
473
    {
474
        return [
475
            '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'],
476
            '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'],
477
        ];
478
    }
479
480
    /**
481
     * @test
482
     * @dataProvider cmpFqdnInvalidDataProvider
483
     */
484
    public function cmpFqdnReturnsFalse($baseHost, $list): void
485
    {
486
        self::assertFalse(GeneralUtility::cmpFQDN($baseHost, $list));
487
    }
488
489
    ///////////////////////////////
490
    // Tests concerning inList
491
    ///////////////////////////////
492
    /**
493
     * @test
494
     * @param string $haystack
495
     * @dataProvider inListForItemContainedReturnsTrueDataProvider
496
     */
497
    public function inListForItemContainedReturnsTrue(string $haystack): void
498
    {
499
        self::assertTrue(GeneralUtility::inList($haystack, 'findme'));
500
    }
501
502
    /**
503
     * Data provider for inListForItemContainedReturnsTrue.
504
     *
505
     * @return array
506
     */
507
    public function inListForItemContainedReturnsTrueDataProvider(): array
508
    {
509
        return [
510
            'Element as second element of four items' => ['one,findme,three,four'],
511
            'Element at beginning of list' => ['findme,one,two'],
512
            'Element at end of list' => ['one,two,findme'],
513
            'One item list' => ['findme'],
514
        ];
515
    }
516
517
    /**
518
     * @test
519
     * @param string $haystack
520
     * @dataProvider inListForItemNotContainedReturnsFalseDataProvider
521
     */
522
    public function inListForItemNotContainedReturnsFalse(string $haystack): void
523
    {
524
        self::assertFalse(GeneralUtility::inList($haystack, 'findme'));
525
    }
526
527
    /**
528
     * Data provider for inListForItemNotContainedReturnsFalse.
529
     *
530
     * @return array
531
     */
532
    public function inListForItemNotContainedReturnsFalseDataProvider(): array
533
    {
534
        return [
535
            'Four item list' => ['one,two,three,four'],
536
            'One item list' => ['one'],
537
            'Empty list' => [''],
538
        ];
539
    }
540
541
    ///////////////////////////////
542
    // Tests concerning expandList
543
    ///////////////////////////////
544
    /**
545
     * @test
546
     * @param string $list
547
     * @param string $expectation
548
     * @dataProvider expandListExpandsIntegerRangesDataProvider
549
     */
550
    public function expandListExpandsIntegerRanges(string $list, string $expectation): void
551
    {
552
        self::assertSame($expectation, GeneralUtility::expandList($list));
553
    }
554
555
    /**
556
     * Data provider for expandListExpandsIntegerRangesDataProvider
557
     *
558
     * @return array
559
     */
560
    public function expandListExpandsIntegerRangesDataProvider(): array
561
    {
562
        return [
563
            'Expand for the same number' => ['1,2-2,7', '1,2,7'],
564
            'Small range expand with parameters reversed ignores reversed items' => ['1,5-3,7', '1,7'],
565
            'Small range expand' => ['1,3-5,7', '1,3,4,5,7'],
566
            'Expand at beginning' => ['3-5,1,7', '3,4,5,1,7'],
567
            'Expand at end' => ['1,7,3-5', '1,7,3,4,5'],
568
            'Multiple small range expands' => ['1,3-5,7-10,12', '1,3,4,5,7,8,9,10,12'],
569
            'One item list' => ['1-5', '1,2,3,4,5'],
570
            'Nothing to expand' => ['1,2,3,4', '1,2,3,4'],
571
            'Empty list' => ['', ''],
572
        ];
573
    }
574
575
    /**
576
     * @test
577
     */
578
    public function expandListExpandsForTwoThousandElementsExpandsOnlyToThousandElementsMaximum(): void
579
    {
580
        $list = GeneralUtility::expandList('1-2000');
581
        self::assertCount(1000, explode(',', $list));
582
    }
583
584
    ///////////////////////////////
585
    // Tests concerning formatSize
586
    ///////////////////////////////
587
    /**
588
     * @test
589
     * @dataProvider formatSizeDataProvider
590
     */
591
    public function formatSizeTranslatesBytesToHigherOrderRepresentation($size, $labels, $base, $expected): void
592
    {
593
        self::assertEquals($expected, GeneralUtility::formatSize($size, $labels, $base));
594
    }
595
596
    /**
597
     * Data provider for formatSizeTranslatesBytesToHigherOrderRepresentation
598
     *
599
     * @return array
600
     */
601
    public function formatSizeDataProvider(): array
602
    {
603
        return [
604
            'IEC Bytes stay bytes (min)' => [1, '', 0, '1 '],
605
            'IEC Bytes stay bytes (max)' => [921, '', 0, '921 '],
606
            'IEC Kilobytes are used (min)' => [922, '', 0, '0.90 Ki'],
607
            'IEC Kilobytes are used (max)' => [943718, '', 0, '922 Ki'],
608
            'IEC Megabytes are used (min)' => [943719, '', 0, '0.90 Mi'],
609
            'IEC Megabytes are used (max)' => [966367641, '', 0, '922 Mi'],
610
            'IEC Gigabytes are used (min)' => [966367642, '', 0, '0.90 Gi'],
611
            'IEC Gigabytes are used (max)' => [989560464998, '', 0, '922 Gi'],
612
            'IEC Decimal is omitted for large kilobytes' => [31080, '', 0, '30 Ki'],
613
            'IEC Decimal is omitted for large megabytes' => [31458000, '', 0, '30 Mi'],
614
            'IEC Decimal is omitted for large gigabytes' => [32212254720, '', 0, '30 Gi'],
615
            'SI Bytes stay bytes (min)' => [1, 'si', 0, '1 '],
616
            'SI Bytes stay bytes (max)' => [899, 'si', 0, '899 '],
617
            'SI Kilobytes are used (min)' => [901, 'si', 0, '0.90 k'],
618
            'SI Kilobytes are used (max)' => [900000, 'si', 0, '900 k'],
619
            'SI Megabytes are used (min)' => [900001, 'si', 0, '0.90 M'],
620
            'SI Megabytes are used (max)' => [900000000, 'si', 0, '900 M'],
621
            'SI Gigabytes are used (min)' => [900000001, 'si', 0, '0.90 G'],
622
            'SI Gigabytes are used (max)' => [900000000000, 'si', 0, '900 G'],
623
            'SI Decimal is omitted for large kilobytes' => [30000, 'si', 0, '30 k'],
624
            'SI Decimal is omitted for large megabytes' => [30000000, 'si', 0, '30 M'],
625
            'SI Decimal is omitted for large gigabytes' => [30000000000, 'si', 0, '30 G'],
626
            'Label for bytes can be exchanged (binary unit)' => [1, ' Foo|||', 0, '1 Foo'],
627
            'Label for kilobytes can be exchanged (binary unit)' => [1024, '| Foo||', 0, '1.00 Foo'],
628
            'Label for megabytes can be exchanged (binary unit)' => [1048576, '|| Foo|', 0, '1.00 Foo'],
629
            'Label for gigabytes can be exchanged (binary unit)' => [1073741824, '||| Foo', 0, '1.00 Foo'],
630
            'Label for bytes can be exchanged (decimal unit)' => [1, ' Foo|||', 1000, '1 Foo'],
631
            'Label for kilobytes can be exchanged (decimal unit)' => [1000, '| Foo||', 1000, '1.00 Foo'],
632
            'Label for megabytes can be exchanged (decimal unit)' => [1000000, '|| Foo|', 1000, '1.00 Foo'],
633
            'Label for gigabytes can be exchanged (decimal unit)' => [1000000000, '||| Foo', 1000, '1.00 Foo'],
634
            'IEC Base is ignored' => [1024, 'iec', 1000, '1.00 Ki'],
635
            'SI Base is ignored' => [1000, 'si', 1024, '1.00 k'],
636
            'Use binary base for unexpected base' => [2048, '| Bar||', 512, '2.00 Bar'],
637
        ];
638
    }
639
640
    ///////////////////////////////
641
    // Tests concerning splitCalc
642
    ///////////////////////////////
643
    /**
644
     * Data provider for splitCalc
645
     *
646
     * @return array expected values, arithmetic expression
647
     */
648
    public function splitCalcDataProvider(): array
649
    {
650
        return [
651
            'empty string returns empty array' => [
652
                [],
653
                '',
654
            ],
655
            'number without operator returns array with plus and number' => [
656
                [['+', '42']],
657
                '42',
658
            ],
659
            'two numbers with asterisk return first number with plus and second number with asterisk' => [
660
                [['+', '42'], ['*', '31']],
661
                '42 * 31',
662
            ],
663
        ];
664
    }
665
666
    /**
667
     * @test
668
     * @dataProvider splitCalcDataProvider
669
     */
670
    public function splitCalcCorrectlySplitsExpression(array $expected, string $expression): void
671
    {
672
        self::assertSame($expected, GeneralUtility::splitCalc($expression, '+-*/'));
673
    }
674
675
    ///////////////////////////////
676
    // Tests concerning htmlspecialchars_decode
677
    ///////////////////////////////
678
    /**
679
     * @test
680
     */
681
    public function htmlspecialcharsDecodeReturnsDecodedString(): void
682
    {
683
        $string = '<typo3 version="6.0">&nbsp;</typo3>';
684
        $encoded = htmlspecialchars($string);
685
        $decoded = htmlspecialchars_decode($encoded);
686
        self::assertEquals($string, $decoded);
687
    }
688
689
    //////////////////////////////////
690
    // Tests concerning validEmail
691
    //////////////////////////////////
692
    /**
693
     * Data provider for valid validEmail's
694
     *
695
     * @return array Valid email addresses
696
     */
697
    public function validEmailValidDataProvider(): array
698
    {
699
        return [
700
            'short mail address' => ['[email protected]'],
701
            'simple mail address' => ['[email protected]'],
702
            'uppercase characters' => ['[email protected]'],
703
            'equal sign in local part' => ['[email protected]'],
704
            'dash in local part' => ['[email protected]'],
705
            'plus in local part' => ['[email protected]'],
706
            'question mark in local part' => ['[email protected]'],
707
            'slash in local part' => ['foo/[email protected]'],
708
            'hash in local part' => ['foo#[email protected]'],
709
            'dot in local part' => ['[email protected]'],
710
            'dash as local part' => ['[email protected]'],
711
            'umlauts in domain part' => ['foo@äöüfoo.com'],
712
            'number as top level domain' => ['[email protected]'],
713
            'top level domain only' => ['test@localhost'],
714
            'umlauts in local part' => ['äöü[email protected]'],
715
            'quoted @ char' => ['"Abc@def"@example.com'],
716
        ];
717
    }
718
719
    /**
720
     * @test
721
     * @dataProvider validEmailValidDataProvider
722
     */
723
    public function validEmailReturnsTrueForValidMailAddress($address): void
724
    {
725
        self::assertTrue(GeneralUtility::validEmail($address));
726
    }
727
728
    /**
729
     * Data provider for invalid validEmail's
730
     *
731
     * @return array Invalid email addresses
732
     */
733
    public function validEmailInvalidDataProvider(): array
734
    {
735
        return [
736
            'empty string' => [''],
737
            'empty array' => [[]],
738
            'integer' => [42],
739
            'float' => [42.23],
740
            'array' => [['foo']],
741
            'object' => [new \stdClass()],
742
            '@ sign only' => ['@'],
743
            'string longer than 320 characters' => [str_repeat('0123456789', 33)],
744
            'duplicate @' => ['test@@example.com'],
745
            'duplicate @ combined with further special characters in local part' => ['test!.!@#$%^&*@example.com'],
746
            'opening parenthesis in local part' => ['foo([email protected]'],
747
            'closing parenthesis in local part' => ['foo)[email protected]'],
748
            'opening square bracket in local part' => ['foo[[email protected]'],
749
            'closing square bracket as local part' => [']@example.com'],
750
            'dash as second level domain' => ['[email protected]'],
751
            'domain part starting with dash' => ['[email protected]'],
752
            'domain part ending with dash' => ['[email protected]'],
753
            'dot at beginning of domain part' => ['[email protected]'],
754
            'local part ends with dot' => ['[email protected]'],
755
            'trailing whitespace' => ['[email protected] '],
756
            'trailing carriage return' => ['[email protected]' . CR],
757
            'trailing linefeed' => ['[email protected]' . LF],
758
            'trailing carriage return linefeed' => ['[email protected]' . CRLF],
759
            'trailing tab' => ['[email protected]' . "\t"],
760
            'prohibited input characters' => ['“mailto:[email protected]”'],
761
            'escaped @ char' => ['abc\@[email protected]'], // known bug, see https://github.com/egulias/EmailValidator/issues/181
762
        ];
763
    }
764
765
    /**
766
     * @test
767
     * @dataProvider validEmailInvalidDataProvider
768
     */
769
    public function validEmailReturnsFalseForInvalidMailAddress($address): void
770
    {
771
        self::assertFalse(GeneralUtility::validEmail($address));
772
    }
773
774
    //////////////////////////////////
775
    // Tests concerning intExplode
776
    //////////////////////////////////
777
    /**
778
     * @test
779
     */
780
    public function intExplodeConvertsStringsToInteger(): void
781
    {
782
        $testString = '1,foo,2';
783
        $expectedArray = [1, 0, 2];
784
        $actualArray = GeneralUtility::intExplode(',', $testString);
785
        self::assertEquals($expectedArray, $actualArray);
786
    }
787
788
    //////////////////////////////////
789
    // Tests concerning implodeArrayForUrl / explodeUrl2Array
790
    //////////////////////////////////
791
    /**
792
     * Data provider for implodeArrayForUrlBuildsValidParameterString
793
     *
794
     * @return array
795
     */
796
    public function implodeArrayForUrlDataProvider(): array
797
    {
798
        $valueArray = ['one' => '√', 'two' => 2];
799
        return [
800
            'Empty input' => ['foo', [], ''],
801
            'String parameters' => ['foo', $valueArray, '&foo[one]=%E2%88%9A&foo[two]=2'],
802
            'Nested array parameters' => ['foo', [$valueArray], '&foo[0][one]=%E2%88%9A&foo[0][two]=2'],
803
            'Keep blank parameters' => ['foo', ['one' => '√', ''], '&foo[one]=%E2%88%9A&foo[0]='],
804
        ];
805
    }
806
807
    /**
808
     * @test
809
     * @dataProvider implodeArrayForUrlDataProvider
810
     */
811
    public function implodeArrayForUrlBuildsValidParameterString($name, $input, $expected): void
812
    {
813
        self::assertSame($expected, GeneralUtility::implodeArrayForUrl($name, $input));
814
    }
815
816
    /**
817
     * @test
818
     */
819
    public function implodeArrayForUrlCanSkipEmptyParameters(): void
820
    {
821
        $input = ['one' => '√', ''];
822
        $expected = '&foo[one]=%E2%88%9A';
823
        self::assertSame($expected, GeneralUtility::implodeArrayForUrl('foo', $input, '', true));
824
    }
825
826
    /**
827
     * @test
828
     */
829
    public function implodeArrayForUrlCanUrlEncodeKeyNames(): void
830
    {
831
        $input = ['one' => '√', ''];
832
        $expected = '&foo%5Bone%5D=%E2%88%9A&foo%5B0%5D=';
833
        self::assertSame($expected, GeneralUtility::implodeArrayForUrl('foo', $input, '', false, true));
834
    }
835
836
    /**
837
     * @test
838
     * @dataProvider explodeUrl2ArrayDataProvider
839
     */
840
    public function explodeUrl2ArrayTransformsParameterStringToFlatArray($input, $expected): void
841
    {
842
        self::assertEquals($expected, GeneralUtility::explodeUrl2Array($input));
843
    }
844
845
    /**
846
     * Data provider for explodeUrl2ArrayTransformsParameterStringToFlatArray
847
     *
848
     * @return array
849
     */
850
    public function explodeUrl2ArrayDataProvider(): array
851
    {
852
        return [
853
            'Empty string' => ['', []],
854
            'Simple parameter string' => ['&one=%E2%88%9A&two=2', ['one' => '√', 'two' => 2]],
855
            'Nested parameter string' => ['&foo[one]=%E2%88%9A&two=2', ['foo[one]' => '√', 'two' => 2]],
856
        ];
857
    }
858
859
    //////////////////////////////////
860
    // Tests concerning revExplode
861
    //////////////////////////////////
862
863
    /**
864
     * @return array
865
     */
866
    public function revExplodeDataProvider(): array
867
    {
868
        return [
869
            'limit 0 should return unexploded string' => [
870
                ':',
871
                'my:words:here',
872
                0,
873
                ['my:words:here'],
874
            ],
875
            'limit 1 should return unexploded string' => [
876
                ':',
877
                'my:words:here',
878
                1,
879
                ['my:words:here'],
880
            ],
881
            'limit 2 should return two pieces' => [
882
                ':',
883
                'my:words:here',
884
                2,
885
                ['my:words', 'here'],
886
            ],
887
            'limit 3 should return unexploded string' => [
888
                ':',
889
                'my:words:here',
890
                3,
891
                ['my', 'words', 'here'],
892
            ],
893
            'limit 0 should return unexploded string if no delimiter is contained' => [
894
                ':',
895
                'mywordshere',
896
                0,
897
                ['mywordshere'],
898
            ],
899
            'limit 1 should return unexploded string if no delimiter is contained' => [
900
                ':',
901
                'mywordshere',
902
                1,
903
                ['mywordshere'],
904
            ],
905
            'limit 2 should return unexploded string if no delimiter is contained' => [
906
                ':',
907
                'mywordshere',
908
                2,
909
                ['mywordshere'],
910
            ],
911
            'limit 3 should return unexploded string if no delimiter is contained' => [
912
                ':',
913
                'mywordshere',
914
                3,
915
                ['mywordshere'],
916
            ],
917
            'multi character delimiter is handled properly with limit 2' => [
918
                '[]',
919
                'a[b][c][d]',
920
                2,
921
                ['a[b][c', 'd]'],
922
            ],
923
            'multi character delimiter is handled properly with limit 3' => [
924
                '[]',
925
                'a[b][c][d]',
926
                3,
927
                ['a[b', 'c', 'd]'],
928
            ],
929
        ];
930
    }
931
932
    /**
933
     * @test
934
     * @dataProvider revExplodeDataProvider
935
     */
936
    public function revExplodeCorrectlyExplodesStringForGivenPartsCount($delimiter, $testString, $count, $expectedArray): void
937
    {
938
        $actualArray = GeneralUtility::revExplode($delimiter, $testString, $count);
939
        self::assertEquals($expectedArray, $actualArray);
940
    }
941
942
    /**
943
     * @test
944
     */
945
    public function revExplodeRespectsLimitThreeWhenExploding(): void
946
    {
947
        $testString = 'even:more:of:my:words:here';
948
        $expectedArray = ['even:more:of:my', 'words', 'here'];
949
        $actualArray = GeneralUtility::revExplode(':', $testString, 3);
950
        self::assertEquals($expectedArray, $actualArray);
951
    }
952
953
    //////////////////////////////////
954
    // Tests concerning trimExplode
955
    //////////////////////////////////
956
    /**
957
     * @test
958
     * @dataProvider trimExplodeReturnsCorrectResultDataProvider
959
     *
960
     * @param string $delimiter
961
     * @param string $testString
962
     * @param bool $removeEmpty
963
     * @param int $limit
964
     * @param array $expectedResult
965
     */
966
    public function trimExplodeReturnsCorrectResult(string $delimiter, string $testString, bool $removeEmpty, int $limit, array $expectedResult): void
967
    {
968
        self::assertSame($expectedResult, GeneralUtility::trimExplode($delimiter, $testString, $removeEmpty, $limit));
969
    }
970
971
    /**
972
     * @return array
973
     */
974
    public function trimExplodeReturnsCorrectResultDataProvider(): array
975
    {
976
        return [
977
            'spaces at element start and end' => [
978
                ',',
979
                ' a , b , c ,d ,,  e,f,',
980
                false,
981
                0,
982
                ['a', 'b', 'c', 'd', '', 'e', 'f', ''],
983
            ],
984
            'removes newline' => [
985
                ',',
986
                ' a , b , ' . LF . ' ,d ,,  e,f,',
987
                true,
988
                0,
989
                ['a', 'b', 'd', 'e', 'f'],
990
            ],
991
            'removes empty elements' => [
992
                ',',
993
                'a , b , c , ,d ,, ,e,f,',
994
                true,
995
                0,
996
                ['a', 'b', 'c', 'd', 'e', 'f'],
997
            ],
998
            'keeps remaining results with empty items after reaching limit with positive parameter' => [
999
                ',',
1000
                ' a , b , c , , d,, ,e ',
1001
                false,
1002
                3,
1003
                ['a', 'b', 'c , , d,, ,e'],
1004
            ],
1005
            'keeps remaining results without empty items after reaching limit with positive parameter' => [
1006
                ',',
1007
                ' a , b , c , , d,, ,e ',
1008
                true,
1009
                3,
1010
                ['a', 'b', 'c , d,e'],
1011
            ],
1012
            'keeps remaining results with empty items after reaching limit with negative parameter' => [
1013
                ',',
1014
                ' a , b , c , d, ,e, f , , ',
1015
                false,
1016
                -3,
1017
                ['a', 'b', 'c', 'd', '', 'e'],
1018
            ],
1019
            'keeps remaining results without empty items after reaching limit with negative parameter' => [
1020
                ',',
1021
                ' a , b , c , d, ,e, f , , ',
1022
                true,
1023
                -3,
1024
                ['a', 'b', 'c'],
1025
            ],
1026
            'returns exact results without reaching limit with positive parameter' => [
1027
                ',',
1028
                ' a , b , , c , , , ',
1029
                true,
1030
                4,
1031
                ['a', 'b', 'c'],
1032
            ],
1033
            'keeps zero as string' => [
1034
                ',',
1035
                'a , b , c , ,d ,, ,e,f, 0 ,',
1036
                true,
1037
                0,
1038
                ['a', 'b', 'c', 'd', 'e', 'f', '0'],
1039
            ],
1040
            'keeps whitespace inside elements' => [
1041
                ',',
1042
                'a , b , c , ,d ,, ,e,f, g h ,',
1043
                true,
1044
                0,
1045
                ['a', 'b', 'c', 'd', 'e', 'f', 'g h'],
1046
            ],
1047
            'can use internal regex delimiter as explode delimiter' => [
1048
                '/',
1049
                'a / b / c / /d // /e/f/ g h /',
1050
                true,
1051
                0,
1052
                ['a', 'b', 'c', 'd', 'e', 'f', 'g h'],
1053
            ],
1054
            'can use whitespaces as delimiter' => [
1055
                ' ',
1056
                '* * * * *',
1057
                true,
1058
                0,
1059
                ['*', '*', '*', '*', '*'],
1060
            ],
1061
            'can use words as delimiter' => [
1062
                'All',
1063
                'HelloAllTogether',
1064
                true,
1065
                0,
1066
                ['Hello', 'Together'],
1067
            ],
1068
            'can use word with appended and prepended spaces as delimiter' => [
1069
                ' all   ',
1070
                'Hello all   together',
1071
                true,
1072
                0,
1073
                ['Hello', 'together'],
1074
            ],
1075
            'can use word with appended and prepended spaces as delimiter and do not remove empty' => [
1076
                ' all   ',
1077
                'Hello all   together     all      there all       all   are  all    none',
1078
                false,
1079
                0,
1080
                ['Hello', 'together', 'there', '', 'are', 'none'],
1081
            ],
1082
            'can use word with appended and prepended spaces as delimiter, do not remove empty and limit' => [
1083
                ' all   ',
1084
                'Hello all   together     all      there all       all   are  all    none',
1085
                false,
1086
                5,
1087
                ['Hello', 'together', 'there', '', 'are  all    none'],
1088
            ],
1089
            'can use word with appended and prepended spaces as delimiter, do not remove empty, limit and multiple delimiter in last' => [
1090
                ' all   ',
1091
                'Hello all   together     all      there all       all   are  all    none',
1092
                false,
1093
                4,
1094
                ['Hello', 'together', 'there', 'all   are  all    none'],
1095
            ],
1096
            'can use word with appended and prepended spaces as delimiter, remove empty and limit' => [
1097
                ' all   ',
1098
                'Hello all   together     all      there all       all   are  all    none',
1099
                true,
1100
                4,
1101
                ['Hello', 'together', 'there', 'are  all    none'],
1102
            ],
1103
            'can use word with appended and prepended spaces as delimiter, remove empty and limit and multiple delimiter in last' => [
1104
                ' all   ',
1105
                'Hello all   together     all      there all       all   are  all    none',
1106
                true,
1107
                5,
1108
                ['Hello', 'together', 'there', 'are' , 'none'],
1109
            ],
1110
            'can use words as delimiter and do not remove empty' => [
1111
                'all  there',
1112
                'Helloall  theretogether  all  there    all  there   are   all  there     none',
1113
                false,
1114
                0,
1115
                ['Hello', 'together', '', 'are', 'none'],
1116
            ],
1117
            'can use words as delimiter, do not remove empty and limit' => [
1118
                'all  there',
1119
                'Helloall  theretogether  all  there    all  there    are   all  there     none',
1120
                false,
1121
                4,
1122
                ['Hello', 'together', '', 'are   all  there     none'],
1123
            ],
1124
            'can use words as delimiter, do not remove empty, limit and multiple delimiter in last' => [
1125
                'all  there',
1126
                'Helloall  theretogether  all  there    all  there    are   all  there     none',
1127
                false,
1128
                3,
1129
                ['Hello', 'together', 'all  there    are   all  there     none'],
1130
            ],
1131
            'can use words as delimiter, remove empty' => [
1132
                'all  there',
1133
                'Helloall  theretogether  all  there    all  there    are   all  there     none',
1134
                true,
1135
                0,
1136
                ['Hello', 'together', 'are', 'none'],
1137
            ],
1138
            'can use words as delimiter, remove empty and limit' => [
1139
                'all  there',
1140
                'Helloall  theretogether  all  there    all  there    are   all  there     none',
1141
                true,
1142
                3,
1143
                ['Hello', 'together', 'are   all  there     none'],
1144
            ],
1145
            'can use words as delimiter, remove empty and limit and multiple delimiter in last' => [
1146
                'all  there',
1147
                'Helloall  theretogether  all  there    all  there    are   all  there     none',
1148
                true,
1149
                4,
1150
                ['Hello', 'together', 'are' , 'none'],
1151
            ],
1152
            'can use new line as delimiter' => [
1153
                LF,
1154
                "Hello\nall\ntogether",
1155
                true,
1156
                0,
1157
                ['Hello', 'all', 'together'],
1158
            ],
1159
            'works with whitespace separator' => [
1160
                "\t",
1161
                " a  b \t c  \t  \t    d  \t  e     \t u j   \t s",
1162
                false,
1163
                0,
1164
                ['a  b', 'c', '', 'd', 'e', 'u j', 's'],
1165
            ],
1166
            'works with whitespace separator and limit' => [
1167
                "\t",
1168
                " a  b \t c  \t  \t    d  \t  e     \t u j   \t s",
1169
                false,
1170
                4,
1171
                ['a  b', 'c', '', "d  \t  e     \t u j   \t s"],
1172
            ],
1173
            'works with whitespace separator and remove empty' => [
1174
                "\t",
1175
                " a  b \t c  \t  \t    d  \t  e     \t u j   \t s",
1176
                true,
1177
                0,
1178
                ['a  b', 'c', 'd', 'e', 'u j', 's'],
1179
            ],
1180
            'works with whitespace separator remove empty and limit' => [
1181
                "\t",
1182
                " a  b \t c  \t  \t    d  \t  e     \t u j   \t s",
1183
                true,
1184
                3,
1185
                ['a  b', 'c', "d  \t  e     \t u j   \t s"],
1186
            ],
1187
        ];
1188
    }
1189
1190
    //////////////////////////////////
1191
    // Tests concerning getBytesFromSizeMeasurement
1192
    //////////////////////////////////
1193
    /**
1194
     * Data provider for getBytesFromSizeMeasurement
1195
     *
1196
     * @return array expected value, input string
1197
     */
1198
    public function getBytesFromSizeMeasurementDataProvider(): array
1199
    {
1200
        return [
1201
            '100 kilo Bytes' => ['102400', '100k'],
1202
            '100 mega Bytes' => ['104857600', '100m'],
1203
            '100 giga Bytes' => ['107374182400', '100g'],
1204
        ];
1205
    }
1206
1207
    /**
1208
     * @test
1209
     * @dataProvider getBytesFromSizeMeasurementDataProvider
1210
     */
1211
    public function getBytesFromSizeMeasurementCalculatesCorrectByteValue($expected, $byteString): void
1212
    {
1213
        self::assertEquals($expected, GeneralUtility::getBytesFromSizeMeasurement($byteString));
1214
    }
1215
1216
    //////////////////////////////////
1217
    // Tests concerning getIndpEnv
1218
    //////////////////////////////////
1219
    /**
1220
     * @test
1221
     */
1222
    public function getIndpEnvTypo3SitePathReturnNonEmptyString(): void
1223
    {
1224
        self::assertTrue(strlen(GeneralUtility::getIndpEnv('TYPO3_SITE_PATH')) >= 1);
1225
    }
1226
1227
    /**
1228
     * @test
1229
     * @requires OSFAMILY Linux|Darwin (path starts with a drive on Windows)
1230
     */
1231
    public function getIndpEnvTypo3SitePathReturnsStringStartingWithSlash(): void
1232
    {
1233
        Environment::initialize(
1234
            Environment::getContext(),
1235
            true,
1236
            false,
1237
            Environment::getProjectPath(),
1238
            Environment::getPublicPath(),
1239
            Environment::getVarPath(),
1240
            Environment::getConfigPath(),
1241
            Environment::getBackendPath() . '/index.php',
1242
            Environment::isWindows() ? 'WINDOWS' : 'UNIX'
1243
        );
1244
        $result = GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
1245
        self::assertEquals('/', $result[0]);
1246
    }
1247
1248
    /**
1249
     * @test
1250
     * @requires OSFAMILY Windows
1251
     */
1252
    public function getIndpEnvTypo3SitePathReturnsStringStartingWithDrive(): void
1253
    {
1254
        $result = GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
1255
        self::assertMatchesRegularExpression('/^[a-z]:\//i', $result);
1256
    }
1257
1258
    /**
1259
     * @test
1260
     */
1261
    public function getIndpEnvTypo3SitePathReturnsStringEndingWithSlash(): void
1262
    {
1263
        $result = GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
1264
        self::assertEquals('/', $result[strlen($result) - 1]);
1265
    }
1266
1267
    /**
1268
     * @return array
1269
     */
1270
    public static function hostnameAndPortDataProvider(): array
1271
    {
1272
        return [
1273
            'localhost ipv4 without port' => ['127.0.0.1', '127.0.0.1', ''],
1274
            'localhost ipv4 with port' => ['127.0.0.1:81', '127.0.0.1', '81'],
1275
            'localhost ipv6 without port' => ['[::1]', '[::1]', ''],
1276
            'localhost ipv6 with port' => ['[::1]:81', '[::1]', '81'],
1277
            'ipv6 without port' => ['[2001:DB8::1]', '[2001:DB8::1]', ''],
1278
            'ipv6 with port' => ['[2001:DB8::1]:81', '[2001:DB8::1]', '81'],
1279
            'hostname without port' => ['lolli.did.this', 'lolli.did.this', ''],
1280
            'hostname with port' => ['lolli.did.this:42', 'lolli.did.this', '42'],
1281
        ];
1282
    }
1283
1284
    /**
1285
     * @test
1286
     * @dataProvider hostnameAndPortDataProvider
1287
     */
1288
    public function getIndpEnvTypo3HostOnlyParsesHostnamesAndIpAddresses($httpHost, $expectedIp): void
1289
    {
1290
        $_SERVER['HTTP_HOST'] = $httpHost;
1291
        self::assertEquals($expectedIp, GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY'));
1292
    }
1293
1294
    /**
1295
     * @test
1296
     * @dataProvider hostnameAndPortDataProvider
1297
     */
1298
    public function getIndpEnvTypo3PortParsesHostnamesAndIpAddresses($httpHost, $dummy, $expectedPort): void
1299
    {
1300
        $_SERVER['HTTP_HOST'] = $httpHost;
1301
        self::assertEquals($expectedPort, GeneralUtility::getIndpEnv('TYPO3_PORT'));
1302
    }
1303
1304
    //////////////////////////////////
1305
    // Tests concerning underscoredToUpperCamelCase
1306
    //////////////////////////////////
1307
    /**
1308
     * Data provider for underscoredToUpperCamelCase
1309
     *
1310
     * @return array expected, input string
1311
     */
1312
    public function underscoredToUpperCamelCaseDataProvider(): array
1313
    {
1314
        return [
1315
            'single word' => ['Blogexample', 'blogexample'],
1316
            'multiple words' => ['BlogExample', 'blog_example'],
1317
        ];
1318
    }
1319
1320
    /**
1321
     * @test
1322
     * @dataProvider underscoredToUpperCamelCaseDataProvider
1323
     */
1324
    public function underscoredToUpperCamelCase($expected, $inputString): void
1325
    {
1326
        self::assertEquals($expected, GeneralUtility::underscoredToUpperCamelCase($inputString));
1327
    }
1328
1329
    //////////////////////////////////
1330
    // Tests concerning underscoredToLowerCamelCase
1331
    //////////////////////////////////
1332
    /**
1333
     * Data provider for underscoredToLowerCamelCase
1334
     *
1335
     * @return array expected, input string
1336
     */
1337
    public function underscoredToLowerCamelCaseDataProvider(): array
1338
    {
1339
        return [
1340
            'single word' => ['minimalvalue', 'minimalvalue'],
1341
            'multiple words' => ['minimalValue', 'minimal_value'],
1342
        ];
1343
    }
1344
1345
    /**
1346
     * @test
1347
     * @dataProvider underscoredToLowerCamelCaseDataProvider
1348
     */
1349
    public function underscoredToLowerCamelCase($expected, $inputString): void
1350
    {
1351
        self::assertEquals($expected, GeneralUtility::underscoredToLowerCamelCase($inputString));
1352
    }
1353
1354
    //////////////////////////////////
1355
    // Tests concerning camelCaseToLowerCaseUnderscored
1356
    //////////////////////////////////
1357
    /**
1358
     * Data provider for camelCaseToLowerCaseUnderscored
1359
     *
1360
     * @return array expected, input string
1361
     */
1362
    public function camelCaseToLowerCaseUnderscoredDataProvider(): array
1363
    {
1364
        return [
1365
            'single word' => ['blogexample', 'blogexample'],
1366
            'single word starting upper case' => ['blogexample', 'Blogexample'],
1367
            'two words starting lower case' => ['minimal_value', 'minimalValue'],
1368
            'two words starting upper case' => ['blog_example', 'BlogExample'],
1369
        ];
1370
    }
1371
1372
    /**
1373
     * @test
1374
     * @dataProvider camelCaseToLowerCaseUnderscoredDataProvider
1375
     */
1376
    public function camelCaseToLowerCaseUnderscored($expected, $inputString): void
1377
    {
1378
        self::assertEquals($expected, GeneralUtility::camelCaseToLowerCaseUnderscored($inputString));
1379
    }
1380
1381
    //////////////////////////////////
1382
    // Tests concerning isValidUrl
1383
    //////////////////////////////////
1384
    /**
1385
     * Data provider for valid isValidUrl's
1386
     *
1387
     * @return array Valid resource
1388
     */
1389
    public function validUrlValidResourceDataProvider(): array
1390
    {
1391
        return [
1392
            'http' => ['http://www.example.org/'],
1393
            'http without trailing slash' => ['http://qwe'],
1394
            'http directory with trailing slash' => ['http://www.example/img/dir/'],
1395
            'http directory without trailing slash' => ['http://www.example/img/dir'],
1396
            'http index.html' => ['http://example.com/index.html'],
1397
            'http index.php' => ['http://www.example.com/index.php'],
1398
            'http test.png' => ['http://www.example/img/test.png'],
1399
            'http username password querystring and anchor' => ['https://user:[email protected]:80/path?arg=value#fragment'],
1400
            'file' => ['file:///tmp/test.c'],
1401
            'file directory' => ['file://foo/bar'],
1402
            'ftp directory' => ['ftp://ftp.example.com/tmp/'],
1403
            'mailto' => ['mailto:[email protected]'],
1404
            'news' => ['news:news.php.net'],
1405
            'telnet' => ['telnet://192.0.2.16:80/'],
1406
            'ldap' => ['ldap://[2001:db8::7]/c=GB?objectClass?one'],
1407
            'http punycode domain name' => ['http://www.xn--bb-eka.at'],
1408
            'http punicode subdomain' => ['http://xn--h-zfa.oebb.at'],
1409
            'http domain-name umlauts' => ['http://www.öbb.at'],
1410
            'http subdomain umlauts' => ['http://äh.oebb.at'],
1411
        ];
1412
    }
1413
1414
    /**
1415
     * @test
1416
     * @dataProvider validUrlValidResourceDataProvider
1417
     */
1418
    public function validURLReturnsTrueForValidResource($url): void
1419
    {
1420
        self::assertTrue(GeneralUtility::isValidUrl($url));
1421
    }
1422
1423
    /**
1424
     * Data provider for invalid isValidUrl's
1425
     *
1426
     * @return array Invalid resource
1427
     */
1428
    public function isValidUrlInvalidResourceDataProvider(): array
1429
    {
1430
        return [
1431
            'http missing colon' => ['http//www.example/wrong/url/'],
1432
            'http missing slash' => ['http:/www.example'],
1433
            'hostname only' => ['www.example.org/'],
1434
            'file missing protocol specification' => ['/tmp/test.c'],
1435
            'slash only' => ['/'],
1436
            'string http://' => ['http://'],
1437
            'string http:/' => ['http:/'],
1438
            'string http:' => ['http:'],
1439
            'string http' => ['http'],
1440
            'empty string' => [''],
1441
            'string -1' => ['-1'],
1442
            'string array()' => ['array()'],
1443
            'random string' => ['qwe'],
1444
            'http directory umlauts' => ['http://www.oebb.at/äöü/'],
1445
            'prohibited input characters' => ['https://{$unresolved_constant}'],
1446
        ];
1447
    }
1448
1449
    /**
1450
     * @test
1451
     * @dataProvider isValidUrlInvalidResourceDataProvider
1452
     */
1453
    public function validURLReturnsFalseForInvalidResource($url): void
1454
    {
1455
        self::assertFalse(GeneralUtility::isValidUrl($url));
1456
    }
1457
1458
    //////////////////////////////////
1459
    // Tests concerning isOnCurrentHost
1460
    //////////////////////////////////
1461
    /**
1462
     * @test
1463
     */
1464
    public function isOnCurrentHostReturnsTrueWithCurrentHost(): void
1465
    {
1466
        $testUrl = GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL');
1467
        self::assertTrue(GeneralUtility::isOnCurrentHost($testUrl));
1468
    }
1469
1470
    /**
1471
     * Data provider for invalid isOnCurrentHost's
1472
     *
1473
     * @return array Invalid Hosts
1474
     */
1475
    public function checkisOnCurrentHostInvalidHosts(): array
1476
    {
1477
        return [
1478
            'empty string' => [''],
1479
            'arbitrary string' => ['arbitrary string'],
1480
            'localhost IP' => ['127.0.0.1'],
1481
            'relative path' => ['./relpath/file.txt'],
1482
            'absolute path' => ['/abspath/file.txt?arg=value'],
1483
            'different host' => [GeneralUtility::getIndpEnv('TYPO3_REQUEST_HOST') . '.example.org'],
1484
        ];
1485
    }
1486
1487
    ////////////////////////////////////////
1488
    // Tests concerning sanitizeLocalUrl
1489
    ////////////////////////////////////////
1490
    /**
1491
     * Data provider for valid sanitizeLocalUrl paths
1492
     *
1493
     * @return array Valid url
1494
     */
1495
    public function sanitizeLocalUrlValidPathsDataProvider(): array
1496
    {
1497
        return [
1498
            'alt_intro.php' => ['alt_intro.php'],
1499
            'alt_intro.php?foo=1&bar=2' => ['alt_intro.php?foo=1&bar=2'],
1500
            '../index.php' => ['../index.php'],
1501
            '../typo3/alt_intro.php' => ['../typo3/alt_intro.php'],
1502
            '../~userDirectory/index.php' => ['../~userDirectory/index.php'],
1503
            '../typo3/index.php?var1=test-case&var2=~user' => ['../typo3/index.php?var1=test-case&var2=~user'],
1504
            Environment::getPublicPath() . '/typo3/alt_intro.php' => [Environment::getPublicPath() . '/typo3/alt_intro.php'],
1505
        ];
1506
    }
1507
1508
    /**
1509
     * @test
1510
     * @param string $path
1511
     * @dataProvider sanitizeLocalUrlValidPathsDataProvider
1512
     */
1513
    public function sanitizeLocalUrlAcceptsNotEncodedValidPaths(string $path): void
1514
    {
1515
        Environment::initialize(
1516
            Environment::getContext(),
1517
            true,
1518
            false,
1519
            Environment::getProjectPath(),
1520
            Environment::getPublicPath(),
1521
            Environment::getVarPath(),
1522
            Environment::getConfigPath(),
1523
            Environment::getBackendPath() . '/index.php',
1524
            Environment::isWindows() ? 'WINDOWS' : 'UNIX'
1525
        );
1526
        self::assertEquals($path, GeneralUtility::sanitizeLocalUrl($path));
1527
    }
1528
1529
    /**
1530
     * @test
1531
     * @param string $path
1532
     * @dataProvider sanitizeLocalUrlValidPathsDataProvider
1533
     */
1534
    public function sanitizeLocalUrlAcceptsEncodedValidPaths(string $path): void
1535
    {
1536
        Environment::initialize(
1537
            Environment::getContext(),
1538
            true,
1539
            false,
1540
            Environment::getProjectPath(),
1541
            Environment::getPublicPath(),
1542
            Environment::getVarPath(),
1543
            Environment::getConfigPath(),
1544
            Environment::getBackendPath() . '/index.php',
1545
            Environment::isWindows() ? 'WINDOWS' : 'UNIX'
1546
        );
1547
        self::assertEquals(rawurlencode($path), GeneralUtility::sanitizeLocalUrl(rawurlencode($path)));
1548
    }
1549
1550
    /**
1551
     * Data provider for valid sanitizeLocalUrl's
1552
     *
1553
     * @return array Valid url
1554
     */
1555
    public function sanitizeLocalUrlValidUrlsDataProvider(): array
1556
    {
1557
        return [
1558
            '/cms/typo3/alt_intro.php' => [
1559
                '/cms/typo3/alt_intro.php',
1560
                'localhost',
1561
                '/cms/',
1562
            ],
1563
            '/cms/index.php' => [
1564
                '/cms/index.php',
1565
                'localhost',
1566
                '/cms/',
1567
            ],
1568
            'http://localhost/typo3/alt_intro.php' => [
1569
                'http://localhost/typo3/alt_intro.php',
1570
                'localhost',
1571
                '',
1572
            ],
1573
            'http://localhost/cms/typo3/alt_intro.php' => [
1574
                'http://localhost/cms/typo3/alt_intro.php',
1575
                'localhost',
1576
                '/cms/',
1577
            ],
1578
        ];
1579
    }
1580
1581
    /**
1582
     * @test
1583
     * @param string $url
1584
     * @param string $host
1585
     * @param string $subDirectory
1586
     * @dataProvider sanitizeLocalUrlValidUrlsDataProvider
1587
     */
1588
    public function sanitizeLocalUrlAcceptsNotEncodedValidUrls(string $url, string $host, string $subDirectory): void
1589
    {
1590
        Environment::initialize(
1591
            Environment::getContext(),
1592
            true,
1593
            false,
1594
            Environment::getProjectPath(),
1595
            Environment::getPublicPath(),
1596
            Environment::getVarPath(),
1597
            Environment::getConfigPath(),
1598
            Environment::getBackendPath() . '/index.php',
1599
            Environment::isWindows() ? 'WINDOWS' : 'UNIX'
1600
        );
1601
        $_SERVER['HTTP_HOST'] = $host;
1602
        $_SERVER['SCRIPT_NAME'] = $subDirectory . 'typo3/index.php';
1603
        self::assertEquals($url, GeneralUtility::sanitizeLocalUrl($url));
1604
    }
1605
1606
    /**
1607
     * @test
1608
     * @param string $url
1609
     * @param string $host
1610
     * @param string $subDirectory
1611
     * @dataProvider sanitizeLocalUrlValidUrlsDataProvider
1612
     */
1613
    public function sanitizeLocalUrlAcceptsEncodedValidUrls(string $url, string $host, string $subDirectory): void
1614
    {
1615
        Environment::initialize(
1616
            Environment::getContext(),
1617
            true,
1618
            false,
1619
            Environment::getProjectPath(),
1620
            Environment::getPublicPath(),
1621
            Environment::getVarPath(),
1622
            Environment::getConfigPath(),
1623
            Environment::getBackendPath() . '/index.php',
1624
            Environment::isWindows() ? 'WINDOWS' : 'UNIX'
1625
        );
1626
        $_SERVER['HTTP_HOST'] = $host;
1627
        $_SERVER['SCRIPT_NAME'] = $subDirectory . 'typo3/index.php';
1628
        self::assertEquals(rawurlencode($url), GeneralUtility::sanitizeLocalUrl(rawurlencode($url)));
1629
    }
1630
1631
    /**
1632
     * Data provider for invalid sanitizeLocalUrl's
1633
     *
1634
     * @return array Valid url
1635
     */
1636
    public function sanitizeLocalUrlInvalidDataProvider(): array
1637
    {
1638
        return [
1639
            'empty string' => [''],
1640
            'http domain' => ['http://www.google.de/'],
1641
            'https domain' => ['https://www.google.de/'],
1642
            'domain without schema' => ['//www.google.de/'],
1643
            'XSS attempt' => ['" onmouseover="alert(123)"'],
1644
            'invalid URL, UNC path' => ['\\\\foo\\bar\\'],
1645
            'invalid URL, HTML break out attempt' => ['" >blabuubb'],
1646
            'base64 encoded string' => ['data:%20text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4='],
1647
        ];
1648
    }
1649
1650
    /**
1651
     * @param string $url
1652
     * @test
1653
     * @dataProvider sanitizeLocalUrlInvalidDataProvider
1654
     */
1655
    public function sanitizeLocalUrlDeniesPlainInvalidUrlsInBackendContext(string $url): void
1656
    {
1657
        Environment::initialize(
1658
            Environment::getContext(),
1659
            true,
1660
            false,
1661
            Environment::getProjectPath(),
1662
            Environment::getPublicPath(),
1663
            Environment::getVarPath(),
1664
            Environment::getConfigPath(),
1665
            Environment::getBackendPath() . '/index.php',
1666
            Environment::isWindows() ? 'WINDOWS' : 'UNIX'
1667
        );
1668
        $_SERVER['HTTP_HOST'] = 'localhost';
1669
        $_SERVER['SCRIPT_NAME'] = 'typo3/index.php';
1670
        self::assertEquals('', GeneralUtility::sanitizeLocalUrl($url));
1671
    }
1672
1673
    /**
1674
     * @param string $url
1675
     * @test
1676
     * @dataProvider sanitizeLocalUrlInvalidDataProvider
1677
     */
1678
    public function sanitizeLocalUrlDeniesPlainInvalidUrlsInFrontendContext(string $url): void
1679
    {
1680
        Environment::initialize(
1681
            Environment::getContext(),
1682
            true,
1683
            false,
1684
            Environment::getProjectPath(),
1685
            Environment::getPublicPath(),
1686
            Environment::getVarPath(),
1687
            Environment::getConfigPath(),
1688
            Environment::getPublicPath() . '/index.php',
1689
            Environment::isWindows() ? 'WINDOWS' : 'UNIX'
1690
        );
1691
        $_SERVER['HTTP_HOST'] = 'localhost';
1692
        $_SERVER['SCRIPT_NAME'] = 'index.php';
1693
        self::assertEquals('', GeneralUtility::sanitizeLocalUrl($url));
1694
    }
1695
1696
    /**
1697
     * @test
1698
     * @dataProvider sanitizeLocalUrlInvalidDataProvider
1699
     */
1700
    public function sanitizeLocalUrlDeniesEncodedInvalidUrls($url): void
1701
    {
1702
        self::assertEquals('', GeneralUtility::sanitizeLocalUrl(rawurlencode($url)));
1703
    }
1704
1705
    ////////////////////////////////////////
1706
    // Tests concerning unlink_tempfile
1707
    ////////////////////////////////////////
1708
1709
    /**
1710
     * @test
1711
     */
1712
    public function unlink_tempfileRemovesValidFileInTypo3temp(): void
1713
    {
1714
        $fixtureFile = __DIR__ . '/Fixtures/clear.gif';
1715
        $testFilename = Environment::getVarPath() . '/tests/' . StringUtility::getUniqueId('test_') . '.gif';
1716
        @copy($fixtureFile, $testFilename);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for copy(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

1716
        /** @scrutinizer ignore-unhandled */ @copy($fixtureFile, $testFilename);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1717
        GeneralUtility::unlink_tempfile($testFilename);
1718
        $fileExists = file_exists($testFilename);
1719
        self::assertFalse($fileExists);
1720
    }
1721
1722
    /**
1723
     * @test
1724
     */
1725
    public function unlink_tempfileRemovesHiddenFile(): void
1726
    {
1727
        $fixtureFile = __DIR__ . '/Fixtures/clear.gif';
1728
        $testFilename = Environment::getVarPath() . '/tests/' . StringUtility::getUniqueId('.test_') . '.gif';
1729
        @copy($fixtureFile, $testFilename);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for copy(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

1729
        /** @scrutinizer ignore-unhandled */ @copy($fixtureFile, $testFilename);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1730
        GeneralUtility::unlink_tempfile($testFilename);
1731
        $fileExists = file_exists($testFilename);
1732
        self::assertFalse($fileExists);
1733
    }
1734
1735
    /**
1736
     * @test
1737
     */
1738
    public function unlink_tempfileReturnsTrueIfFileWasRemoved(): void
1739
    {
1740
        $fixtureFile = __DIR__ . '/Fixtures/clear.gif';
1741
        $testFilename = Environment::getVarPath() . '/tests/' . StringUtility::getUniqueId('test_') . '.gif';
1742
        @copy($fixtureFile, $testFilename);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for copy(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

1742
        /** @scrutinizer ignore-unhandled */ @copy($fixtureFile, $testFilename);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1743
        $returnValue = GeneralUtility::unlink_tempfile($testFilename);
1744
        self::assertTrue($returnValue);
1745
    }
1746
1747
    /**
1748
     * @test
1749
     */
1750
    public function unlink_tempfileReturnsNullIfFileDoesNotExist(): void
1751
    {
1752
        $returnValue = GeneralUtility::unlink_tempfile(Environment::getVarPath() . '/tests/' . StringUtility::getUniqueId('i_do_not_exist'));
1753
        self::assertNull($returnValue);
1754
    }
1755
1756
    /**
1757
     * @test
1758
     */
1759
    public function unlink_tempfileReturnsNullIfFileIsNowWithinTypo3temp(): void
1760
    {
1761
        $returnValue = GeneralUtility::unlink_tempfile('/tmp/typo3-unit-test-unlink_tempfile');
1762
        self::assertNull($returnValue);
1763
    }
1764
1765
    //////////////////////////////////////
1766
    // Tests concerning tempnam
1767
    //////////////////////////////////////
1768
1769
    /**
1770
     * @test
1771
     */
1772
    public function tempnamReturnsPathStartingWithGivenPrefix(): void
1773
    {
1774
        $filePath = GeneralUtility::tempnam('foo');
1775
        $this->testFilesToDelete[] = $filePath;
1776
        $fileName = basename($filePath);
1777
        self::assertStringStartsWith('foo', $fileName);
1778
    }
1779
1780
    /**
1781
     * @test
1782
     */
1783
    public function tempnamReturnsPathWithoutBackslashes(): void
1784
    {
1785
        $filePath = GeneralUtility::tempnam('foo');
1786
        $this->testFilesToDelete[] = $filePath;
1787
        self::assertStringNotContainsString('\\', $filePath);
1788
    }
1789
1790
    /**
1791
     * @test
1792
     */
1793
    public function tempnamReturnsAbsolutePathInVarPath(): void
1794
    {
1795
        $filePath = GeneralUtility::tempnam('foo');
1796
        $this->testFilesToDelete[] = $filePath;
1797
        self::assertStringStartsWith(Environment::getVarPath() . '/transient/', $filePath);
1798
    }
1799
1800
    //////////////////////////////////////
1801
    // Tests concerning removeDotsFromTS
1802
    //////////////////////////////////////
1803
    /**
1804
     * @test
1805
     */
1806
    public function removeDotsFromTypoScriptSucceedsWithDottedArray(): void
1807
    {
1808
        $typoScript = [
1809
            'propertyA.' => [
1810
                'keyA.' => [
1811
                    'valueA' => 1,
1812
                ],
1813
                'keyB' => 2,
1814
            ],
1815
            'propertyB' => 3,
1816
        ];
1817
        $expectedResult = [
1818
            'propertyA' => [
1819
                'keyA' => [
1820
                    'valueA' => 1,
1821
                ],
1822
                'keyB' => 2,
1823
            ],
1824
            'propertyB' => 3,
1825
        ];
1826
        self::assertEquals($expectedResult, GeneralUtility::removeDotsFromTS($typoScript));
1827
    }
1828
1829
    /**
1830
     * @test
1831
     */
1832
    public function removeDotsFromTypoScriptOverridesSubArray(): void
1833
    {
1834
        $typoScript = [
1835
            'propertyA.' => [
1836
                'keyA' => 'getsOverridden',
1837
                'keyA.' => [
1838
                    'valueA' => 1,
1839
                ],
1840
                'keyB' => 2,
1841
            ],
1842
            'propertyB' => 3,
1843
        ];
1844
        $expectedResult = [
1845
            'propertyA' => [
1846
                'keyA' => [
1847
                    'valueA' => 1,
1848
                ],
1849
                'keyB' => 2,
1850
            ],
1851
            'propertyB' => 3,
1852
        ];
1853
        self::assertEquals($expectedResult, GeneralUtility::removeDotsFromTS($typoScript));
1854
    }
1855
1856
    /**
1857
     * @test
1858
     */
1859
    public function removeDotsFromTypoScriptOverridesWithScalar(): void
1860
    {
1861
        $typoScript = [
1862
            'propertyA.' => [
1863
                'keyA.' => [
1864
                    'valueA' => 1,
1865
                ],
1866
                'keyA' => 'willOverride',
1867
                'keyB' => 2,
1868
            ],
1869
            'propertyB' => 3,
1870
        ];
1871
        $expectedResult = [
1872
            'propertyA' => [
1873
                'keyA' => 'willOverride',
1874
                'keyB' => 2,
1875
            ],
1876
            'propertyB' => 3,
1877
        ];
1878
        self::assertEquals($expectedResult, GeneralUtility::removeDotsFromTS($typoScript));
1879
    }
1880
1881
    //////////////////////////////////////
1882
    // Tests concerning get_dirs
1883
    //////////////////////////////////////
1884
    /**
1885
     * @test
1886
     */
1887
    public function getDirsReturnsArrayOfDirectoriesFromGivenDirectory(): void
1888
    {
1889
        $directories = GeneralUtility::get_dirs(Environment::getLegacyConfigPath() . '/');
1890
        self::assertIsArray($directories);
1891
    }
1892
1893
    /**
1894
     * @test
1895
     */
1896
    public function getDirsReturnsStringErrorOnPathFailure(): void
1897
    {
1898
        $path = 'foo';
1899
        $result = GeneralUtility::get_dirs($path);
1900
        $expectedResult = 'error';
1901
        self::assertEquals($expectedResult, $result);
1902
    }
1903
1904
    //////////////////////////////////
1905
    // Tests concerning hmac
1906
    //////////////////////////////////
1907
    /**
1908
     * @test
1909
     */
1910
    public function hmacReturnsHashOfProperLength(): void
1911
    {
1912
        $hmac = GeneralUtility::hmac('message');
1913
        self::assertTrue(!empty($hmac) && is_string($hmac));
1914
        self::assertEquals(strlen($hmac), 40);
1915
    }
1916
1917
    /**
1918
     * @test
1919
     */
1920
    public function hmacReturnsEqualHashesForEqualInput(): void
1921
    {
1922
        $msg0 = 'message';
1923
        $msg1 = 'message';
1924
        self::assertEquals(GeneralUtility::hmac($msg0), GeneralUtility::hmac($msg1));
1925
    }
1926
1927
    /**
1928
     * @test
1929
     */
1930
    public function hmacReturnsNoEqualHashesForNonEqualInput(): void
1931
    {
1932
        $msg0 = 'message0';
1933
        $msg1 = 'message1';
1934
        self::assertNotEquals(GeneralUtility::hmac($msg0), GeneralUtility::hmac($msg1));
1935
    }
1936
1937
    //////////////////////////////////
1938
    // Tests concerning quoteJSvalue
1939
    //////////////////////////////////
1940
    /**
1941
     * Data provider for quoteJSvalueTest.
1942
     *
1943
     * @return array
1944
     */
1945
    public function quoteJsValueDataProvider(): array
1946
    {
1947
        return [
1948
            'Immune characters are returned as is' => [
1949
                '._,',
1950
                '._,',
1951
            ],
1952
            'Alphanumerical characters are returned as is' => [
1953
                'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
1954
                'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
1955
            ],
1956
            'Angle brackets and ampersand are encoded' => [
1957
                '<>&',
1958
                '\\u003C\\u003E\\u0026',
1959
            ],
1960
            'Quotes and backslashes are encoded' => [
1961
                '"\'\\',
1962
                '\\u0022\\u0027\\u005C',
1963
            ],
1964
            'Forward slashes are escaped' => [
1965
                '</script>',
1966
                '\\u003C\\/script\\u003E',
1967
            ],
1968
            'Empty string stays empty' => [
1969
                '',
1970
                '',
1971
            ],
1972
            'Exclamation mark and space are properly encoded' => [
1973
                'Hello World!',
1974
                'Hello\\u0020World\\u0021',
1975
            ],
1976
            'Whitespaces are properly encoded' => [
1977
                "\t" . LF . CR . ' ',
1978
                '\\u0009\\u000A\\u000D\\u0020',
1979
            ],
1980
            'Null byte is properly encoded' => [
1981
                "\0",
1982
                '\\u0000',
1983
            ],
1984
            'Umlauts are properly encoded' => [
1985
                'ÜüÖöÄä',
1986
                '\\u00dc\\u00fc\\u00d6\\u00f6\\u00c4\\u00e4',
1987
            ],
1988
        ];
1989
    }
1990
1991
    /**
1992
     * @test
1993
     * @param string $input
1994
     * @param string $expected
1995
     * @dataProvider quoteJsValueDataProvider
1996
     */
1997
    public function quoteJsValueTest(string $input, string $expected): void
1998
    {
1999
        self::assertSame('\'' . $expected . '\'', GeneralUtility::quoteJSvalue($input));
2000
    }
2001
2002
    public static function jsonEncodeForHtmlAttributeTestDataProvider(): array
2003
    {
2004
        return [
2005
            [
2006
                ['html' => '<tag attr="\\Vendor\\Package">value</tag>'],
2007
                true,
2008
                // cave: `\\\\` (four) actually required for PHP only, will be `\\` (two) in HTML
2009
                '{&quot;html&quot;:&quot;\u003Ctag attr=\u0022\\\\Vendor\\\\Package\u0022\u003Evalue\u003C\/tag\u003E&quot;}',
2010
            ],
2011
            [
2012
                ['html' => '<tag attr="\\Vendor\\Package">value</tag>'],
2013
                false,
2014
                // cave: `\\\\` (four) actually required for PHP only, will be `\\` (two) in HTML
2015
                '{"html":"\u003Ctag attr=\u0022\\\\Vendor\\\\Package\u0022\u003Evalue\u003C\/tag\u003E"}',
2016
            ],
2017
            [
2018
                ['spaces' => '|' . chr(9) . '|' . chr(10) . '|' . chr(13) . '|'],
2019
                false,
2020
                '{"spaces":"|\t|\n|\r|"}',
2021
            ],
2022
        ];
2023
    }
2024
2025
    /**
2026
     * @test
2027
     * @dataProvider jsonEncodeForHtmlAttributeTestDataProvider
2028
     */
2029
    public function jsonEncodeForHtmlAttributeTest($value, bool $useHtmlEntities, string $expectation): void
2030
    {
2031
        self::assertSame($expectation, GeneralUtility::jsonEncodeForHtmlAttribute($value, $useHtmlEntities));
2032
    }
2033
2034
    public static function jsonEncodeForJavaScriptTestDataProvider(): array
2035
    {
2036
        return [
2037
            [
2038
                ['html' => '<tag attr="\\Vendor\\Package">value</tag>'],
2039
                // cave: `\\\\` (four) actually required for PHP only, will be `\\` (two) in JavaScript
2040
                '{"html":"\u003Ctag attr=\u0022\\\\u005CVendor\\\\u005CPackage\u0022\u003Evalue\u003C\/tag\u003E"}',
2041
            ],
2042
            [
2043
                ['spaces' => '|' . chr(9) . '|' . chr(10) . '|' . chr(13) . '|'],
2044
                '{"spaces":"|\u0009|\u000A|\u000D|"}',
2045
            ],
2046
        ];
2047
    }
2048
2049
    /**
2050
     * @test
2051
     * @dataProvider jsonEncodeForJavaScriptTestDataProvider
2052
     */
2053
    public function jsonEncodeForJavaScriptTest($value, string $expectation): void
2054
    {
2055
        self::assertSame($expectation, GeneralUtility::jsonEncodeForJavaScript($value));
2056
    }
2057
2058
    ///////////////////////////////
2059
    // Tests concerning fixPermissions
2060
    ///////////////////////////////
2061
    /**
2062
     * @test
2063
     * @requires function posix_getegid
2064
     */
2065
    public function fixPermissionsSetsGroup(): void
2066
    {
2067
        if (Environment::isWindows()) {
2068
            self::markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2069
        }
2070
        // Create and prepare test file
2071
        $filename = $this->getVirtualTestDir() . '/' . StringUtility::getUniqueId('test_');
2072
        GeneralUtilityFilesystemFixture::writeFileToTypo3tempDir($filename, '42');
2073
        $currentGroupId = posix_getegid();
2074
        // Set target group and run method
2075
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup'] = $currentGroupId;
2076
        GeneralUtilityFilesystemFixture::fixPermissions($filename);
2077
        clearstatcache();
2078
        self::assertEquals($currentGroupId, filegroup($filename));
2079
    }
2080
2081
    /**
2082
     * @test
2083
     */
2084
    public function fixPermissionsSetsPermissionsToFile(): void
2085
    {
2086
        if (Environment::isWindows()) {
2087
            self::markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2088
        }
2089
        // Create and prepare test file
2090
        $filename = $this->getVirtualTestDir() . '/' . StringUtility::getUniqueId('test_');
2091
        GeneralUtilityFilesystemFixture::writeFileToTypo3tempDir($filename, '42');
2092
        chmod($filename, 482);
2093
        // Set target permissions and run method
2094
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'] = '0660';
2095
        $fixPermissionsResult = GeneralUtilityFilesystemFixture::fixPermissions($filename);
2096
        clearstatcache();
2097
        self::assertTrue($fixPermissionsResult);
2098
        self::assertEquals('0660', substr(decoct(fileperms($filename)), 2));
2099
    }
2100
2101
    /**
2102
     * @test
2103
     */
2104
    public function fixPermissionsSetsPermissionsToHiddenFile(): void
2105
    {
2106
        if (Environment::isWindows()) {
2107
            self::markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2108
        }
2109
        // Create and prepare test file
2110
        $filename = $this->getVirtualTestDir() . '/' . StringUtility::getUniqueId('test_');
2111
        GeneralUtilityFilesystemFixture::writeFileToTypo3tempDir($filename, '42');
2112
        chmod($filename, 482);
2113
        // Set target permissions and run method
2114
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'] = '0660';
2115
        $fixPermissionsResult = GeneralUtilityFilesystemFixture::fixPermissions($filename);
2116
        clearstatcache();
2117
        self::assertTrue($fixPermissionsResult);
2118
        self::assertEquals('0660', substr(decoct(fileperms($filename)), 2));
2119
    }
2120
2121
    /**
2122
     * @test
2123
     */
2124
    public function fixPermissionsSetsPermissionsToDirectory(): void
2125
    {
2126
        if (Environment::isWindows()) {
2127
            self::markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2128
        }
2129
        // Create and prepare test directory
2130
        $directory = $this->getVirtualTestDir() . '/' . StringUtility::getUniqueId('test_');
2131
        GeneralUtilityFilesystemFixture::mkdir($directory);
2132
        chmod($directory, 1551);
2133
        // Set target permissions and run method
2134
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] = '0770';
2135
        $fixPermissionsResult = GeneralUtilityFilesystemFixture::fixPermissions($directory);
2136
        clearstatcache();
2137
        self::assertTrue($fixPermissionsResult);
2138
        self::assertEquals('0770', substr(decoct(fileperms($directory)), 1));
2139
    }
2140
2141
    /**
2142
     * @test
2143
     */
2144
    public function fixPermissionsSetsPermissionsToDirectoryWithTrailingSlash(): void
2145
    {
2146
        if (Environment::isWindows()) {
2147
            self::markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2148
        }
2149
        // Create and prepare test directory
2150
        $directory = $this->getVirtualTestDir() . '/' . StringUtility::getUniqueId('test_');
2151
        GeneralUtilityFilesystemFixture::mkdir($directory);
2152
        chmod($directory, 1551);
2153
        // Set target permissions and run method
2154
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] = '0770';
2155
        $fixPermissionsResult = GeneralUtilityFilesystemFixture::fixPermissions($directory . '/');
2156
        // Get actual permissions and clean up
2157
        clearstatcache();
2158
        self::assertTrue($fixPermissionsResult);
2159
        self::assertEquals('0770', substr(decoct(fileperms($directory)), 1));
2160
    }
2161
2162
    /**
2163
     * @test
2164
     */
2165
    public function fixPermissionsSetsPermissionsToHiddenDirectory(): void
2166
    {
2167
        if (Environment::isWindows()) {
2168
            self::markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2169
        }
2170
        // Create and prepare test directory
2171
        $directory = $this->getVirtualTestDir() . '/' . StringUtility::getUniqueId('test_');
2172
        GeneralUtilityFilesystemFixture::mkdir($directory);
2173
        chmod($directory, 1551);
2174
        // Set target permissions and run method
2175
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] = '0770';
2176
        $fixPermissionsResult = GeneralUtilityFilesystemFixture::fixPermissions($directory);
2177
        // Get actual permissions and clean up
2178
        clearstatcache();
2179
        self::assertTrue($fixPermissionsResult);
2180
        self::assertEquals('0770', substr(decoct(fileperms($directory)), 1));
2181
    }
2182
2183
    /**
2184
     * @test
2185
     */
2186
    public function fixPermissionsCorrectlySetsPermissionsRecursive(): void
2187
    {
2188
        if (Environment::isWindows()) {
2189
            self::markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2190
        }
2191
        // Create and prepare test directory and file structure
2192
        $baseDirectory = $this->getVirtualTestDir() . '/' . StringUtility::getUniqueId('test_');
2193
        GeneralUtilityFilesystemFixture::mkdir($baseDirectory);
2194
        chmod($baseDirectory, 1751);
2195
        GeneralUtilityFilesystemFixture::writeFileToTypo3tempDir($baseDirectory . '/file', '42');
2196
        chmod($baseDirectory . '/file', 482);
2197
        GeneralUtilityFilesystemFixture::mkdir($baseDirectory . '/foo');
2198
        chmod($baseDirectory . '/foo', 1751);
2199
        GeneralUtilityFilesystemFixture::writeFileToTypo3tempDir($baseDirectory . '/foo/file', '42');
2200
        chmod($baseDirectory . '/foo/file', 482);
2201
        GeneralUtilityFilesystemFixture::mkdir($baseDirectory . '/.bar');
2202
        chmod($baseDirectory . '/.bar', 1751);
2203
        // Use this if writeFileToTypo3tempDir is fixed to create hidden files in subdirectories
2204
        // \TYPO3\CMS\Core\Utility\GeneralUtility::writeFileToTypo3tempDir($baseDirectory . '/.bar/.file', '42');
2205
        // \TYPO3\CMS\Core\Utility\GeneralUtility::writeFileToTypo3tempDir($baseDirectory . '/.bar/..file2', '42');
2206
        touch($baseDirectory . '/.bar/.file', 42);
2207
        chmod($baseDirectory . '/.bar/.file', 482);
2208
        touch($baseDirectory . '/.bar/..file2', 42);
2209
        chmod($baseDirectory . '/.bar/..file2', 482);
2210
        // Set target permissions and run method
2211
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'] = '0660';
2212
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] = '0770';
2213
        $fixPermissionsResult = GeneralUtilityFilesystemFixture::fixPermissions($baseDirectory, true);
2214
        // Get actual permissions
2215
        clearstatcache();
2216
        $resultBaseDirectoryPermissions = substr(decoct(fileperms($baseDirectory)), 1);
2217
        $resultBaseFilePermissions = substr(decoct(fileperms($baseDirectory . '/file')), 2);
2218
        $resultFooDirectoryPermissions = substr(decoct(fileperms($baseDirectory . '/foo')), 1);
2219
        $resultFooFilePermissions = substr(decoct(fileperms($baseDirectory . '/foo/file')), 2);
2220
        $resultBarDirectoryPermissions = substr(decoct(fileperms($baseDirectory . '/.bar')), 1);
2221
        $resultBarFilePermissions = substr(decoct(fileperms($baseDirectory . '/.bar/.file')), 2);
2222
        $resultBarFile2Permissions = substr(decoct(fileperms($baseDirectory . '/.bar/..file2')), 2);
2223
        // Test if everything was ok
2224
        self::assertTrue($fixPermissionsResult);
2225
        self::assertEquals('0770', $resultBaseDirectoryPermissions);
2226
        self::assertEquals('0660', $resultBaseFilePermissions);
2227
        self::assertEquals('0770', $resultFooDirectoryPermissions);
2228
        self::assertEquals('0660', $resultFooFilePermissions);
2229
        self::assertEquals('0770', $resultBarDirectoryPermissions);
2230
        self::assertEquals('0660', $resultBarFilePermissions);
2231
        self::assertEquals('0660', $resultBarFile2Permissions);
2232
    }
2233
2234
    /**
2235
     * @test
2236
     */
2237
    public function fixPermissionsDoesNotSetPermissionsToNotAllowedPath(): void
2238
    {
2239
        if (Environment::isWindows()) {
2240
            self::markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2241
        }
2242
        // Create and prepare test file
2243
        $filename = Environment::getVarPath() . '/tests/../../../typo3temp/var/tests/' . StringUtility::getUniqueId('test_');
2244
        // Set target permissions and run method
2245
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'] = '0660';
2246
        $fixPermissionsResult = GeneralUtility::fixPermissions($filename);
2247
        self::assertFalse($fixPermissionsResult);
2248
    }
2249
2250
    /**
2251
     * @test
2252
     */
2253
    public function fixPermissionsSetsPermissionsWithRelativeFileReference(): void
2254
    {
2255
        if (Environment::isWindows()) {
2256
            self::markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2257
        }
2258
        $filename = 'typo3temp/var/tests/' . StringUtility::getUniqueId('test_');
2259
        GeneralUtility::writeFileToTypo3tempDir(Environment::getPublicPath() . '/' . $filename, '42');
2260
        $this->testFilesToDelete[] = Environment::getPublicPath() . '/' . $filename;
2261
        chmod(Environment::getPublicPath() . '/' . $filename, 482);
2262
        // Set target permissions and run method
2263
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'] = '0660';
2264
        $fixPermissionsResult = GeneralUtility::fixPermissions($filename);
2265
        clearstatcache();
2266
        self::assertTrue($fixPermissionsResult);
2267
        self::assertEquals('0660', substr(decoct(fileperms(Environment::getPublicPath() . '/' . $filename)), 2));
2268
    }
2269
2270
    /**
2271
     * @test
2272
     */
2273
    public function fixPermissionsSetsDefaultPermissionsToFile(): void
2274
    {
2275
        if (Environment::isWindows()) {
2276
            self::markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2277
        }
2278
        $filename = $this->getVirtualTestDir() . '/' . StringUtility::getUniqueId('test_');
2279
        GeneralUtilityFilesystemFixture::writeFileToTypo3tempDir($filename, '42');
2280
        chmod($filename, 482);
2281
        unset($GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask']);
2282
        $fixPermissionsResult = GeneralUtilityFilesystemFixture::fixPermissions($filename);
2283
        clearstatcache();
2284
        self::assertTrue($fixPermissionsResult);
2285
        self::assertEquals('0644', substr(decoct(fileperms($filename)), 2));
2286
    }
2287
2288
    /**
2289
     * @test
2290
     */
2291
    public function fixPermissionsSetsDefaultPermissionsToDirectory(): void
2292
    {
2293
        if (Environment::isWindows()) {
2294
            self::markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2295
        }
2296
        $directory = $this->getVirtualTestDir() . '/' . StringUtility::getUniqueId('test_');
2297
        GeneralUtilityFilesystemFixture::mkdir($directory);
2298
        chmod($directory, 1551);
2299
        unset($GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask']);
2300
        $fixPermissionsResult = GeneralUtilityFilesystemFixture::fixPermissions($directory);
2301
        clearstatcache();
2302
        self::assertTrue($fixPermissionsResult);
2303
        self::assertEquals('0755', substr(decoct(fileperms($directory)), 1));
2304
    }
2305
2306
    ///////////////////////////////
2307
    // Tests concerning mkdir
2308
    ///////////////////////////////
2309
    /**
2310
     * @test
2311
     */
2312
    public function mkdirCreatesDirectory(): void
2313
    {
2314
        $directory = $this->getVirtualTestDir() . '/' . StringUtility::getUniqueId('test_');
2315
        $mkdirResult = GeneralUtilityFilesystemFixture::mkdir($directory);
2316
        clearstatcache();
2317
        self::assertTrue($mkdirResult);
2318
        self::assertDirectoryExists($directory);
2319
    }
2320
2321
    /**
2322
     * @test
2323
     */
2324
    public function mkdirCreatesHiddenDirectory(): void
2325
    {
2326
        $directory = $this->getVirtualTestDir() . '/' . StringUtility::getUniqueId('.test_');
2327
        $mkdirResult = GeneralUtilityFilesystemFixture::mkdir($directory);
2328
        clearstatcache();
2329
        self::assertTrue($mkdirResult);
2330
        self::assertDirectoryExists($directory);
2331
    }
2332
2333
    /**
2334
     * @test
2335
     */
2336
    public function mkdirCreatesDirectoryWithTrailingSlash(): void
2337
    {
2338
        $directory = $this->getVirtualTestDir() . '/' . StringUtility::getUniqueId('test_') . '/';
2339
        $mkdirResult = GeneralUtilityFilesystemFixture::mkdir($directory);
2340
        clearstatcache();
2341
        self::assertTrue($mkdirResult);
2342
        self::assertDirectoryExists($directory);
2343
    }
2344
2345
    /**
2346
     * @test
2347
     */
2348
    public function mkdirSetsPermissionsOfCreatedDirectory(): void
2349
    {
2350
        if (Environment::isWindows()) {
2351
            self::markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2352
        }
2353
        $directory = $this->getVirtualTestDir() . '/' . StringUtility::getUniqueId('test_');
2354
        $oldUmask = umask(19);
2355
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] = '0772';
2356
        GeneralUtilityFilesystemFixture::mkdir($directory);
2357
        clearstatcache();
2358
        $resultDirectoryPermissions = substr(decoct(fileperms($directory)), 1);
2359
        umask($oldUmask);
2360
        self::assertEquals('0772', $resultDirectoryPermissions);
2361
    }
2362
2363
    /**
2364
     * @test
2365
     */
2366
    public function mkdirSetsGroupOwnershipOfCreatedDirectory(): void
2367
    {
2368
        $swapGroup = $this->checkGroups(__FUNCTION__);
2369
        if ($swapGroup !== false) {
2370
            $GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup'] = $swapGroup;
2371
            $directory = $this->getVirtualTestDir() . '/' . StringUtility::getUniqueId('mkdirtest_');
2372
            GeneralUtilityFilesystemFixture::mkdir($directory);
2373
            clearstatcache();
2374
            $resultDirectoryGroup = filegroup($directory);
2375
            self::assertEquals($resultDirectoryGroup, $swapGroup);
2376
        }
2377
    }
2378
2379
    ///////////////////////////////
2380
    // Helper function for filesystem ownership tests
2381
    ///////////////////////////////
2382
    /**
2383
     * Check if test on filesystem group ownership can be done in this environment
2384
     * If so, return second group of webserver user
2385
     *
2386
     * @param string $methodName calling method name
2387
     * @return mixed FALSE if test cannot be run, int group id of the second group of webserver user
2388
     * @requires function posix_getegid
2389
     * @requires function posix_getgroups
2390
     */
2391
    private function checkGroups(string $methodName)
2392
    {
2393
        if (Environment::isWindows()) {
2394
            self::markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2395
            return false;
2396
        }
2397
        $groups = posix_getgroups();
2398
        if (count($groups) <= 1) {
2399
            self::markTestSkipped($methodName . '() test cannot be done when the web server user is only member of 1 group.');
2400
            return false;
2401
        }
2402
        $secondaryGroups = array_diff($groups, [posix_getegid()]);
2403
        return array_shift($secondaryGroups);
2404
    }
2405
2406
    /////////////////////////////////////////////
2407
    // Tests concerning writeFileToTypo3tempDir()
2408
    /////////////////////////////////////////////
2409
2410
    /**
2411
     * @return array
2412
     */
2413
    public function invalidFilePathForTypo3tempDirDataProvider(): array
2414
    {
2415
        return [
2416
            [
2417
                Environment::getPublicPath() . '/../path/this-path-has-more-than-60-characters-in-one-base-path-you-can-even-count-more',
2418
                'Input filepath "' . Environment::getPublicPath() . '/../path/this-path-has-more-than-60-characters-in-one-base-path-you-can-even-count-more" was generally invalid!',
2419
            ],
2420
            [
2421
                Environment::getPublicPath() . '/dummy/path/this-path-has-more-than-60-characters-in-one-base-path-you-can-even-count-more',
2422
                'Input filepath "' . Environment::getPublicPath() . '/dummy/path/this-path-has-more-than-60-characters-in-one-base-path-you-can-even-count-more" was generally invalid!',
2423
            ],
2424
            [
2425
                Environment::getPublicPath() . '/dummy/path/this-path-has-more-than-60-characters-in-one-base-path-you-can-even-count-more',
2426
                'Input filepath "' . Environment::getPublicPath() . '/dummy/path/this-path-has-more-than-60-characters-in-one-base-path-you-can-even-count-more" was generally invalid!',
2427
            ],
2428
            [
2429
                '/dummy/path/awesome',
2430
                '"/dummy/path/" was not within directory Environment::getPublicPath() + "/typo3temp/"',
2431
            ],
2432
            [
2433
                Environment::getLegacyConfigPath() . '/path',
2434
                '"' . Environment::getLegacyConfigPath() . '/" was not within directory Environment::getPublicPath() + "/typo3temp/"',
2435
            ],
2436
            [
2437
                Environment::getPublicPath() . '/typo3temp/táylor/swíft',
2438
                'Subdir, "táylor/", was NOT on the form "[[:alnum:]_]/+"',
2439
            ],
2440
            'Path instead of file given' => [
2441
                Environment::getPublicPath() . '/typo3temp/dummy/path/',
2442
                'Calculated file location didn\'t match input "' . Environment::getPublicPath() . '/typo3temp/dummy/path/".',
2443
            ],
2444
        ];
2445
    }
2446
2447
    /**
2448
     * @test
2449
     * @dataProvider invalidFilePathForTypo3tempDirDataProvider
2450
     * @param string $invalidFilePath
2451
     * @param string $expectedResult
2452
     */
2453
    public function writeFileToTypo3tempDirFailsWithInvalidPath(string $invalidFilePath, string $expectedResult): void
2454
    {
2455
        $result = GeneralUtility::writeFileToTypo3tempDir($invalidFilePath, 'dummy content to be written');
2456
        self::assertSame($result, $expectedResult);
2457
    }
2458
2459
    /**
2460
     * @return array
2461
     */
2462
    public function validFilePathForTypo3tempDirDataProvider(): array
2463
    {
2464
        return [
2465
            'Default text file' => [
2466
                Environment::getVarPath() . '/paranoid/android.txt',
2467
            ],
2468
            'Html file extension' => [
2469
                Environment::getVarPath() . '/karma.html',
2470
            ],
2471
            'No file extension' => [
2472
                Environment::getVarPath() . '/no-surprises',
2473
            ],
2474
            'Deep directory' => [
2475
                Environment::getVarPath() . '/climbing/up/the/walls',
2476
            ],
2477
            'File in typo3temp/var directory' => [
2478
                Environment::getPublicPath() . '/typo3temp/var/path/foo.txt',
2479
            ],
2480
        ];
2481
    }
2482
2483
    /**
2484
     * @test
2485
     * @dataProvider validFilePathForTypo3tempDirDataProvider
2486
     * @param string $filePath
2487
     */
2488
    public function writeFileToTypo3tempDirWorksWithValidPath(string $filePath): void
2489
    {
2490
        $dummyContent = 'Please could you stop the noise, I\'m trying to get some rest from all the unborn chicken voices in my head.';
2491
2492
        $this->testFilesToDelete[] = $filePath;
2493
2494
        $result = GeneralUtility::writeFileToTypo3tempDir($filePath, $dummyContent);
2495
2496
        self::assertNull($result);
2497
        self::assertFileExists($filePath);
2498
        self::assertStringEqualsFile($filePath, $dummyContent);
2499
    }
2500
2501
    ///////////////////////////////
2502
    // Tests concerning mkdir_deep
2503
    ///////////////////////////////
2504
    /**
2505
     * @test
2506
     */
2507
    public function mkdirDeepCreatesDirectory(): void
2508
    {
2509
        $directory = $this->getVirtualTestDir() . '/' . StringUtility::getUniqueId('test_');
2510
        GeneralUtility::mkdir_deep($directory);
2511
        self::assertDirectoryExists($directory);
2512
    }
2513
2514
    /**
2515
     * @test
2516
     */
2517
    public function mkdirDeepCreatesSubdirectoriesRecursive(): void
2518
    {
2519
        $directory = $this->getVirtualTestDir() . 'typo3temp/var/tests/' . StringUtility::getUniqueId('test_');
2520
        $subDirectory = $directory . '/foo';
2521
        GeneralUtility::mkdir_deep($subDirectory);
2522
        self::assertDirectoryExists($subDirectory);
2523
    }
2524
2525
    /**
2526
     * Data provider for mkdirDeepCreatesDirectoryWithDoubleSlashes.
2527
     * @return array
2528
     */
2529
    public function mkdirDeepCreatesDirectoryWithAndWithoutDoubleSlashesDataProvider(): array
2530
    {
2531
        return [
2532
            'no double slash if concatenated with Environment::getPublicPath()' => ['fileadmin/testDir1'],
2533
            'double slash if concatenated with Environment::getPublicPath()' => ['/fileadmin/testDir2'],
2534
        ];
2535
    }
2536
2537
    /**
2538
     * @test
2539
     * @dataProvider mkdirDeepCreatesDirectoryWithAndWithoutDoubleSlashesDataProvider
2540
     */
2541
    public function mkdirDeepCreatesDirectoryWithDoubleSlashes($directoryToCreate): void
2542
    {
2543
        vfsStream::setup('root', null, ['public' => []]);
2544
        GeneralUtility::mkdir_deep('vfs://root/public/' . $directoryToCreate);
2545
        self::assertDirectoryExists('vfs://root/public/' . $directoryToCreate);
2546
    }
2547
2548
    /**
2549
     * @test
2550
     */
2551
    public function mkdirDeepFixesPermissionsOfCreatedDirectory(): void
2552
    {
2553
        if (Environment::isWindows()) {
2554
            self::markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2555
        }
2556
        $directory = StringUtility::getUniqueId('mkdirdeeptest_');
2557
        $oldUmask = umask(19);
2558
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] = '0777';
2559
        GeneralUtility::mkdir_deep(Environment::getVarPath() . '/tests/' . $directory);
2560
        $this->testFilesToDelete[] = Environment::getVarPath() . '/tests/' . $directory;
2561
        clearstatcache();
2562
        umask($oldUmask);
2563
        self::assertEquals('777', substr(decoct(fileperms(Environment::getVarPath() . '/tests/' . $directory)), -3, 3));
2564
    }
2565
2566
    /**
2567
     * @test
2568
     */
2569
    public function mkdirDeepFixesPermissionsOnNewParentDirectory(): void
2570
    {
2571
        if (Environment::isWindows()) {
2572
            self::markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2573
        }
2574
        $directory = StringUtility::getUniqueId('mkdirdeeptest_');
2575
        $subDirectory = $directory . '/bar';
2576
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] = '0777';
2577
        $oldUmask = umask(19);
2578
        GeneralUtility::mkdir_deep(Environment::getVarPath() . '/tests/' . $subDirectory);
2579
        $this->testFilesToDelete[] = Environment::getVarPath() . '/tests/' . $directory;
2580
        clearstatcache();
2581
        umask($oldUmask);
2582
        self::assertEquals('777', substr(decoct(fileperms(Environment::getVarPath() . '/tests/' . $directory)), -3, 3));
2583
    }
2584
2585
    /**
2586
     * @test
2587
     */
2588
    public function mkdirDeepDoesNotChangePermissionsOfExistingSubDirectories(): void
2589
    {
2590
        if (Environment::isWindows()) {
2591
            self::markTestSkipped(self::NO_FIX_PERMISSIONS_ON_WINDOWS);
2592
        }
2593
        $baseDirectory = Environment::getVarPath() . '/tests/';
2594
        $existingDirectory = StringUtility::getUniqueId('test_existing_') . '/';
2595
        $newSubDirectory = StringUtility::getUniqueId('test_new_');
2596
        @mkdir($baseDirectory . $existingDirectory);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for mkdir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

2596
        /** @scrutinizer ignore-unhandled */ @mkdir($baseDirectory . $existingDirectory);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2597
        $this->testFilesToDelete[] = $baseDirectory . $existingDirectory;
2598
        chmod($baseDirectory . $existingDirectory, 482);
2599
        GeneralUtility::mkdir_deep($baseDirectory . $existingDirectory . $newSubDirectory);
2600
        self::assertEquals(742, (int)substr(decoct(fileperms($baseDirectory . $existingDirectory)), 2));
2601
    }
2602
2603
    /**
2604
     * @test
2605
     */
2606
    public function mkdirDeepSetsGroupOwnershipOfCreatedDirectory(): void
2607
    {
2608
        $swapGroup = $this->checkGroups(__FUNCTION__);
2609
        if ($swapGroup !== false) {
2610
            $GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup'] = $swapGroup;
2611
            $directory = StringUtility::getUniqueId('mkdirdeeptest_');
2612
            GeneralUtility::mkdir_deep(Environment::getVarPath() . '/tests/' . $directory);
2613
            $this->testFilesToDelete[] = Environment::getVarPath() . '/tests/' . $directory;
2614
            clearstatcache();
2615
            $resultDirectoryGroup = filegroup(Environment::getVarPath() . '/tests/' . $directory);
2616
            self::assertEquals($resultDirectoryGroup, $swapGroup);
2617
        }
2618
    }
2619
2620
    /**
2621
     * @test
2622
     */
2623
    public function mkdirDeepSetsGroupOwnershipOfCreatedParentDirectory(): void
2624
    {
2625
        $swapGroup = $this->checkGroups(__FUNCTION__);
2626
        if ($swapGroup !== false) {
2627
            $GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup'] = $swapGroup;
2628
            $directory = StringUtility::getUniqueId('mkdirdeeptest_');
2629
            $subDirectory = $directory . '/bar';
2630
            GeneralUtility::mkdir_deep(Environment::getVarPath() . '/tests/' . $subDirectory);
2631
            $this->testFilesToDelete[] = Environment::getVarPath() . '/tests/' . $directory;
2632
            clearstatcache();
2633
            $resultDirectoryGroup = filegroup(Environment::getVarPath() . '/tests/' . $directory);
2634
            self::assertEquals($resultDirectoryGroup, $swapGroup);
2635
        }
2636
    }
2637
2638
    /**
2639
     * @test
2640
     */
2641
    public function mkdirDeepSetsGroupOwnershipOnNewSubDirectory(): void
2642
    {
2643
        $swapGroup = $this->checkGroups(__FUNCTION__);
2644
        if ($swapGroup !== false) {
2645
            $GLOBALS['TYPO3_CONF_VARS']['SYS']['createGroup'] = $swapGroup;
2646
            $directory = StringUtility::getUniqueId('mkdirdeeptest_');
2647
            $subDirectory = $directory . '/bar';
2648
            GeneralUtility::mkdir_deep(Environment::getVarPath() . '/tests/' . $subDirectory);
2649
            $this->testFilesToDelete[] = Environment::getVarPath() . '/tests/' . $directory;
2650
            clearstatcache();
2651
            $resultDirectoryGroup = filegroup(Environment::getVarPath() . '/tests/' . $directory);
2652
            self::assertEquals($resultDirectoryGroup, $swapGroup);
2653
        }
2654
    }
2655
2656
    /**
2657
     * @test
2658
     */
2659
    public function mkdirDeepCreatesDirectoryInVfsStream(): void
2660
    {
2661
        vfsStreamWrapper::register();
2662
        $baseDirectory = StringUtility::getUniqueId('test_');
2663
        vfsStreamWrapper::setRoot(new vfsStreamDirectory($baseDirectory));
2664
        GeneralUtility::mkdir_deep('vfs://' . $baseDirectory . '/sub');
2665
        self::assertDirectoryExists('vfs://' . $baseDirectory . '/sub');
2666
    }
2667
2668
    /**
2669
     * @test
2670
     */
2671
    public function mkdirDeepThrowsExceptionIfDirectoryCreationFails(): void
2672
    {
2673
        $this->expectException(\RuntimeException::class);
2674
        $this->expectExceptionCode(1170251401);
2675
2676
        GeneralUtility::mkdir_deep('http://localhost');
2677
    }
2678
2679
    /**
2680
     * @test
2681
     */
2682
    public function mkdirDeepThrowsExceptionIfBaseDirectoryIsNotOfTypeString(): void
2683
    {
2684
        $this->expectException(\InvalidArgumentException::class);
2685
        $this->expectExceptionCode(1303662955);
2686
2687
        GeneralUtility::mkdir_deep([]);
0 ignored issues
show
Bug introduced by
array() of type array is incompatible with the type string expected by parameter $directory of TYPO3\CMS\Core\Utility\G...alUtility::mkdir_deep(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2687
        GeneralUtility::mkdir_deep(/** @scrutinizer ignore-type */ []);
Loading history...
2688
    }
2689
2690
    ///////////////////////////////
2691
    // Tests concerning rmdir
2692
    ///////////////////////////////
2693
2694
    /**
2695
     * @test
2696
     */
2697
    public function rmdirRemovesFile(): void
2698
    {
2699
        $file = Environment::getVarPath() . '/tests/' . StringUtility::getUniqueId('file_');
2700
        touch($file);
2701
        GeneralUtility::rmdir($file);
2702
        self::assertFileDoesNotExist($file);
2703
    }
2704
2705
    /**
2706
     * @test
2707
     */
2708
    public function rmdirReturnTrueIfFileWasRemoved(): void
2709
    {
2710
        $file = Environment::getVarPath() . '/tests/' . StringUtility::getUniqueId('file_');
2711
        touch($file);
2712
        self::assertTrue(GeneralUtility::rmdir($file));
2713
    }
2714
2715
    /**
2716
     * @test
2717
     */
2718
    public function rmdirReturnFalseIfNoFileWasRemoved(): void
2719
    {
2720
        $file = Environment::getVarPath() . '/tests/' . StringUtility::getUniqueId('file_');
2721
        self::assertFalse(GeneralUtility::rmdir($file));
2722
    }
2723
2724
    /**
2725
     * @test
2726
     */
2727
    public function rmdirRemovesDirectory(): void
2728
    {
2729
        $directory = Environment::getVarPath() . '/tests/' . StringUtility::getUniqueId('directory_');
2730
        mkdir($directory);
2731
        GeneralUtility::rmdir($directory);
2732
        self::assertFileDoesNotExist($directory);
2733
    }
2734
2735
    /**
2736
     * @test
2737
     */
2738
    public function rmdirRemovesDirectoryWithTrailingSlash(): void
2739
    {
2740
        $directory = Environment::getVarPath() . '/tests/' . StringUtility::getUniqueId('directory_') . '/';
2741
        mkdir($directory);
2742
        GeneralUtility::rmdir($directory);
2743
        self::assertFileDoesNotExist($directory);
2744
    }
2745
2746
    /**
2747
     * @test
2748
     */
2749
    public function rmdirDoesNotRemoveDirectoryWithFilesAndReturnsFalseIfRecursiveDeletionIsOff(): void
2750
    {
2751
        $directory = Environment::getVarPath() . '/tests/' . StringUtility::getUniqueId('directory_') . '/';
2752
        mkdir($directory);
2753
        $file = StringUtility::getUniqueId('file_');
2754
        touch($directory . $file);
2755
        $this->testFilesToDelete[] = $directory;
2756
        $return = GeneralUtility::rmdir($directory);
2757
        self::assertFileExists($directory);
2758
        self::assertFileExists($directory . $file);
2759
        self::assertFalse($return);
2760
    }
2761
2762
    /**
2763
     * @test
2764
     */
2765
    public function rmdirRemovesDirectoriesRecursiveAndReturnsTrue(): void
2766
    {
2767
        $directory = Environment::getVarPath() . '/tests/' . StringUtility::getUniqueId('directory_') . '/';
2768
        mkdir($directory);
2769
        mkdir($directory . 'sub/');
2770
        touch($directory . 'sub/file');
2771
        $return = GeneralUtility::rmdir($directory, true);
2772
        self::assertFileDoesNotExist($directory);
2773
        self::assertTrue($return);
2774
    }
2775
2776
    /**
2777
     * @test
2778
     */
2779
    public function rmdirRemovesLinkToDirectory(): void
2780
    {
2781
        $existingDirectory = Environment::getVarPath() . '/tests/' . StringUtility::getUniqueId('notExists_') . '/';
2782
        mkdir($existingDirectory);
2783
        $this->testFilesToDelete[] = $existingDirectory;
2784
        $symlinkName = Environment::getVarPath() . '/tests/' . StringUtility::getUniqueId('link_');
2785
        symlink($existingDirectory, $symlinkName);
2786
        GeneralUtility::rmdir($symlinkName, true);
2787
        self::assertFalse(is_link($symlinkName));
2788
    }
2789
2790
    /**
2791
     * @test
2792
     */
2793
    public function rmdirRemovesDeadLinkToDirectory(): void
2794
    {
2795
        $notExistingDirectory = Environment::getVarPath() . '/tests/' . StringUtility::getUniqueId('notExists_') . '/';
2796
        $symlinkName = Environment::getVarPath() . '/tests/' . StringUtility::getUniqueId('link_');
2797
        mkdir($notExistingDirectory);
2798
        symlink($notExistingDirectory, $symlinkName);
2799
        rmdir($notExistingDirectory);
2800
2801
        GeneralUtility::rmdir($symlinkName, true);
2802
        self::assertFalse(is_link($symlinkName));
2803
    }
2804
2805
    /**
2806
     * @test
2807
     */
2808
    public function rmdirRemovesDeadLinkToFile(): void
2809
    {
2810
        $notExistingFile = Environment::getVarPath() . '/tests/' . StringUtility::getUniqueId('notExists_');
2811
        $symlinkName = Environment::getVarPath() . '/tests/' . StringUtility::getUniqueId('link_');
2812
        touch($notExistingFile);
2813
        symlink($notExistingFile, $symlinkName);
2814
        unlink($notExistingFile);
2815
        GeneralUtility::rmdir($symlinkName, true);
2816
        self::assertFalse(is_link($symlinkName));
2817
    }
2818
2819
    ///////////////////////////////////
2820
    // Tests concerning getFilesInDir
2821
    ///////////////////////////////////
2822
2823
    /**
2824
     * Helper method to create test directory.
2825
     *
2826
     * @return string A unique directory name prefixed with test_.
2827
     */
2828
    protected function getFilesInDirCreateTestDirectory(): string
2829
    {
2830
        $structure = [
2831
            'subDirectory' => [
2832
                'test.php' => 'butter',
2833
                'other.php' => 'milk',
2834
                'stuff.csv' => 'honey',
2835
            ],
2836
            'excludeMe.txt' => 'cocoa nibs',
2837
            'double.setup.typoscript' => 'cool TS',
2838
            'testB.txt' => 'olive oil',
2839
            'testA.txt' => 'eggs',
2840
            'testC.txt' => 'carrots',
2841
            'test.js' => 'oranges',
2842
            'test.css' => 'apples',
2843
            '.secret.txt' => 'sammon',
2844
        ];
2845
        vfsStream::setup('test', null, $structure);
2846
        $vfsUrl = vfsStream::url('test');
2847
2848
        // set random values for mtime
2849
        foreach ($structure as $structureLevel1Key => $structureLevel1Content) {
2850
            $newMtime = random_int(0, mt_getrandmax());
2851
            if (is_array($structureLevel1Content)) {
2852
                foreach ($structureLevel1Content as $structureLevel2Key => $structureLevel2Content) {
2853
                    touch($vfsUrl . '/' . $structureLevel1Key . '/' . $structureLevel2Key, $newMtime);
2854
                }
2855
            } else {
2856
                touch($vfsUrl . '/' . $structureLevel1Key, $newMtime);
2857
            }
2858
        }
2859
2860
        return $vfsUrl;
2861
    }
2862
2863
    /**
2864
     * @test
2865
     */
2866
    public function getFilesInDirFindsRegularFile(): void
2867
    {
2868
        $vfsStreamUrl = $this->getFilesInDirCreateTestDirectory();
2869
        $files = GeneralUtility::getFilesInDir($vfsStreamUrl);
2870
        self::assertContains('testA.txt', $files);
0 ignored issues
show
Bug introduced by
$files of type string is incompatible with the type iterable expected by parameter $haystack of PHPUnit\Framework\Assert::assertContains(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2870
        self::assertContains('testA.txt', /** @scrutinizer ignore-type */ $files);
Loading history...
2871
    }
2872
2873
    /**
2874
     * @test
2875
     */
2876
    public function getFilesInDirFindsHiddenFile(): void
2877
    {
2878
        $vfsStreamUrl = $this->getFilesInDirCreateTestDirectory();
2879
        $files = GeneralUtility::getFilesInDir($vfsStreamUrl);
2880
        self::assertContains('.secret.txt', $files);
0 ignored issues
show
Bug introduced by
$files of type string is incompatible with the type iterable expected by parameter $haystack of PHPUnit\Framework\Assert::assertContains(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2880
        self::assertContains('.secret.txt', /** @scrutinizer ignore-type */ $files);
Loading history...
2881
    }
2882
2883
    /**
2884
     * Data provider for getFilesInDirByExtensionFindsFiles
2885
     *
2886
     * @return array
2887
     */
2888
    public function fileExtensionDataProvider(): array
2889
    {
2890
        return [
2891
            'no space' => [
2892
                'setup.typoscript,txt,js,css',
2893
            ],
2894
            'spaces' => [
2895
                'setup.typoscript, txt, js, css',
2896
            ],
2897
            'mixed' => [
2898
                'setup.typoscript , txt,js, css',
2899
            ],
2900
            'wild' => [
2901
                'setup.typoscript,  txt,     js  ,         css',
2902
            ],
2903
        ];
2904
    }
2905
2906
    /**
2907
     * @dataProvider fileExtensionDataProvider
2908
     * @test
2909
     */
2910
    public function getFilesInDirByExtensionFindsFiles($fileExtensions): void
2911
    {
2912
        $vfsStreamUrl = $this->getFilesInDirCreateTestDirectory();
2913
        $files = GeneralUtility::getFilesInDir($vfsStreamUrl, $fileExtensions);
2914
        self::assertContains('double.setup.typoscript', $files);
0 ignored issues
show
Bug introduced by
$files of type string is incompatible with the type iterable expected by parameter $haystack of PHPUnit\Framework\Assert::assertContains(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2914
        self::assertContains('double.setup.typoscript', /** @scrutinizer ignore-type */ $files);
Loading history...
2915
        self::assertContains('testA.txt', $files);
2916
        self::assertContains('test.js', $files);
2917
        self::assertContains('test.css', $files);
2918
    }
2919
2920
    /**
2921
     * @test
2922
     */
2923
    public function getFilesInDirByExtensionDoesNotFindFilesWithOtherExtensions(): void
2924
    {
2925
        $vfsStreamUrl = $this->getFilesInDirCreateTestDirectory();
2926
        $files = GeneralUtility::getFilesInDir($vfsStreamUrl, 'txt,js');
2927
        self::assertContains('testA.txt', $files);
0 ignored issues
show
Bug introduced by
$files of type string is incompatible with the type iterable expected by parameter $haystack of PHPUnit\Framework\Assert::assertContains(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2927
        self::assertContains('testA.txt', /** @scrutinizer ignore-type */ $files);
Loading history...
2928
        self::assertContains('test.js', $files);
2929
        self::assertNotContains('test.css', $files);
0 ignored issues
show
Bug introduced by
$files of type string is incompatible with the type iterable expected by parameter $haystack of PHPUnit\Framework\Assert::assertNotContains(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2929
        self::assertNotContains('test.css', /** @scrutinizer ignore-type */ $files);
Loading history...
2930
    }
2931
2932
    /**
2933
     * @test
2934
     */
2935
    public function getFilesInDirExcludesFilesMatchingPattern(): void
2936
    {
2937
        $vfsStreamUrl = $this->getFilesInDirCreateTestDirectory();
2938
        $files = GeneralUtility::getFilesInDir($vfsStreamUrl, '', false, '', 'excludeMe.*');
2939
        self::assertContains('test.js', $files);
0 ignored issues
show
Bug introduced by
$files of type string is incompatible with the type iterable expected by parameter $haystack of PHPUnit\Framework\Assert::assertContains(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2939
        self::assertContains('test.js', /** @scrutinizer ignore-type */ $files);
Loading history...
2940
        self::assertNotContains('excludeMe.txt', $files);
0 ignored issues
show
Bug introduced by
$files of type string is incompatible with the type iterable expected by parameter $haystack of PHPUnit\Framework\Assert::assertNotContains(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2940
        self::assertNotContains('excludeMe.txt', /** @scrutinizer ignore-type */ $files);
Loading history...
2941
    }
2942
2943
    /**
2944
     * @test
2945
     */
2946
    public function getFilesInDirCanPrependPath(): void
2947
    {
2948
        $vfsStreamUrl = $this->getFilesInDirCreateTestDirectory();
2949
        self::assertContains(
2950
            $vfsStreamUrl . '/testA.txt',
2951
            GeneralUtility::getFilesInDir($vfsStreamUrl, '', true)
0 ignored issues
show
Bug introduced by
TYPO3\CMS\Core\Utility\G...vfsStreamUrl, '', true) of type string is incompatible with the type iterable expected by parameter $haystack of PHPUnit\Framework\Assert::assertContains(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2951
            /** @scrutinizer ignore-type */ GeneralUtility::getFilesInDir($vfsStreamUrl, '', true)
Loading history...
2952
        );
2953
    }
2954
2955
    /**
2956
     * @test
2957
     */
2958
    public function getFilesInDirDoesSortAlphabeticallyByDefault(): void
2959
    {
2960
        $vfsStreamUrl = $this->getFilesInDirCreateTestDirectory();
2961
        self::assertSame(
2962
            array_values(GeneralUtility::getFilesInDir($vfsStreamUrl, '', false)),
0 ignored issues
show
Bug introduced by
TYPO3\CMS\Core\Utility\G...fsStreamUrl, '', false) of type string is incompatible with the type array expected by parameter $array of array_values(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2962
            array_values(/** @scrutinizer ignore-type */ GeneralUtility::getFilesInDir($vfsStreamUrl, '', false)),
Loading history...
2963
            ['.secret.txt', 'double.setup.typoscript', 'excludeMe.txt', 'test.css', 'test.js', 'testA.txt', 'testB.txt', 'testC.txt']
2964
        );
2965
    }
2966
2967
    /**
2968
     * @test
2969
     */
2970
    public function getFilesInDirCanOrderByMtime(): void
2971
    {
2972
        $vfsStreamUrl = $this->getFilesInDirCreateTestDirectory();
2973
        $files = [];
2974
        $iterator = new \DirectoryIterator($vfsStreamUrl);
2975
        foreach ($iterator as $fileinfo) {
2976
            if ($fileinfo->isFile()) {
2977
                $files[$fileinfo->getFilename()] = $fileinfo->getMTime();
2978
            }
2979
        }
2980
        asort($files);
2981
        self::assertSame(
2982
            array_values(GeneralUtility::getFilesInDir($vfsStreamUrl, '', false, 'mtime')),
0 ignored issues
show
Bug introduced by
TYPO3\CMS\Core\Utility\G...rl, '', false, 'mtime') of type string is incompatible with the type array expected by parameter $array of array_values(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2982
            array_values(/** @scrutinizer ignore-type */ GeneralUtility::getFilesInDir($vfsStreamUrl, '', false, 'mtime')),
Loading history...
2983
            array_keys($files)
2984
        );
2985
    }
2986
2987
    /**
2988
     * @test
2989
     */
2990
    public function getFilesInDirReturnsArrayWithMd5OfElementAndPathAsArrayKey(): void
2991
    {
2992
        $vfsStreamUrl = $this->getFilesInDirCreateTestDirectory();
2993
        self::assertArrayHasKey(
2994
            md5($vfsStreamUrl . '/testA.txt'),
2995
            GeneralUtility::getFilesInDir($vfsStreamUrl)
0 ignored issues
show
Bug introduced by
TYPO3\CMS\Core\Utility\G...lesInDir($vfsStreamUrl) of type string is incompatible with the type ArrayAccess|array expected by parameter $array of PHPUnit\Framework\Assert::assertArrayHasKey(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2995
            /** @scrutinizer ignore-type */ GeneralUtility::getFilesInDir($vfsStreamUrl)
Loading history...
2996
        );
2997
    }
2998
2999
    /**
3000
     * @test
3001
     */
3002
    public function getFilesInDirDoesNotFindDirectories(): void
3003
    {
3004
        $vfsStreamUrl = $this->getFilesInDirCreateTestDirectory();
3005
        self::assertNotContains(
3006
            'subDirectory',
3007
            GeneralUtility::getFilesInDir($vfsStreamUrl)
0 ignored issues
show
Bug introduced by
TYPO3\CMS\Core\Utility\G...lesInDir($vfsStreamUrl) of type string is incompatible with the type iterable expected by parameter $haystack of PHPUnit\Framework\Assert::assertNotContains(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3007
            /** @scrutinizer ignore-type */ GeneralUtility::getFilesInDir($vfsStreamUrl)
Loading history...
3008
        );
3009
    }
3010
3011
    /**
3012
     * Dotfiles; current directory: '.' and parent directory: '..' must not be
3013
     * present.
3014
     *
3015
     * @test
3016
     */
3017
    public function getFilesInDirDoesNotFindDotfiles(): void
3018
    {
3019
        $vfsStreamUrl = $this->getFilesInDirCreateTestDirectory();
3020
        $files = GeneralUtility::getFilesInDir($vfsStreamUrl);
3021
        self::assertNotContains('..', $files);
0 ignored issues
show
Bug introduced by
$files of type string is incompatible with the type iterable expected by parameter $haystack of PHPUnit\Framework\Assert::assertNotContains(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3021
        self::assertNotContains('..', /** @scrutinizer ignore-type */ $files);
Loading history...
3022
        self::assertNotContains('.', $files);
3023
    }
3024
3025
    ///////////////////////////////
3026
    // Tests concerning split_fileref
3027
    ///////////////////////////////
3028
    /**
3029
     * @test
3030
     */
3031
    public function splitFileRefReturnsFileTypeNotForFolders(): void
3032
    {
3033
        $directoryName = StringUtility::getUniqueId('test_') . '.com';
3034
        $directoryPath = Environment::getVarPath() . '/tests/';
3035
        $directory = $directoryPath . $directoryName;
3036
        mkdir($directory, octdec($GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask']));
0 ignored issues
show
Bug introduced by
It seems like octdec($GLOBALS['TYPO3_C...']['folderCreateMask']) can also be of type double; however, parameter $permissions of mkdir() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3036
        mkdir($directory, /** @scrutinizer ignore-type */ octdec($GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask']));
Loading history...
3037
        $fileInfo = GeneralUtility::split_fileref($directory);
3038
        $directoryCreated = is_dir($directory);
3039
        rmdir($directory);
3040
        self::assertTrue($directoryCreated);
3041
        self::assertIsArray($fileInfo);
3042
        self::assertEquals($directoryPath, $fileInfo['path']);
3043
        self::assertEquals($directoryName, $fileInfo['file']);
3044
        self::assertEquals($directoryName, $fileInfo['filebody']);
3045
        self::assertEquals('', $fileInfo['fileext']);
3046
        self::assertArrayNotHasKey('realFileext', $fileInfo);
3047
    }
3048
3049
    /**
3050
     * @test
3051
     */
3052
    public function splitFileRefReturnsFileTypeForFilesWithoutPathSite(): void
3053
    {
3054
        $testFile = 'fileadmin/media/someFile.png';
3055
        $fileInfo = GeneralUtility::split_fileref($testFile);
3056
        self::assertIsArray($fileInfo);
3057
        self::assertEquals('fileadmin/media/', $fileInfo['path']);
3058
        self::assertEquals('someFile.png', $fileInfo['file']);
3059
        self::assertEquals('someFile', $fileInfo['filebody']);
3060
        self::assertEquals('png', $fileInfo['fileext']);
3061
    }
3062
3063
    /////////////////////////////
3064
    // Tests concerning dirname
3065
    /////////////////////////////
3066
    /**
3067
     * @see dirnameWithDataProvider
3068
     * @return array|array[]
3069
     */
3070
    public function dirnameDataProvider(): array
3071
    {
3072
        return [
3073
            'absolute path with multiple part and file' => ['/dir1/dir2/script.php', '/dir1/dir2'],
3074
            'absolute path with one part' => ['/dir1/', '/dir1'],
3075
            'absolute path to file without extension' => ['/dir1/something', '/dir1'],
3076
            'relative path with one part and file' => ['dir1/script.php', 'dir1'],
3077
            'relative one-character path with one part and file' => ['d/script.php', 'd'],
3078
            'absolute zero-part path with file' => ['/script.php', ''],
3079
            'empty string' => ['', ''],
3080
        ];
3081
    }
3082
3083
    /**
3084
     * @test
3085
     * @dataProvider dirnameDataProvider
3086
     * @param string $input the input for dirname
3087
     * @param string $expectedValue the expected return value expected from dirname
3088
     */
3089
    public function dirnameWithDataProvider(string $input, string $expectedValue): void
3090
    {
3091
        self::assertEquals($expectedValue, GeneralUtility::dirname($input));
3092
    }
3093
3094
    /////////////////////////////////////
3095
    // Tests concerning resolveBackPath
3096
    /////////////////////////////////////
3097
    /**
3098
     * @see resolveBackPathWithDataProvider
3099
     * @return array|array[]
3100
     */
3101
    public function resolveBackPathDataProvider(): array
3102
    {
3103
        return [
3104
            'empty path' => ['', ''],
3105
            'this directory' => ['./', './'],
3106
            'relative directory without ..' => ['dir1/dir2/dir3/', 'dir1/dir2/dir3/'],
3107
            'relative path without ..' => ['dir1/dir2/script.php', 'dir1/dir2/script.php'],
3108
            'absolute directory without ..' => ['/dir1/dir2/dir3/', '/dir1/dir2/dir3/'],
3109
            'absolute path without ..' => ['/dir1/dir2/script.php', '/dir1/dir2/script.php'],
3110
            'only one directory upwards without trailing slash' => ['..', '..'],
3111
            'only one directory upwards with trailing slash' => ['../', '../'],
3112
            'one level with trailing ..' => ['dir1/..', ''],
3113
            'one level with trailing ../' => ['dir1/../', ''],
3114
            'two levels with trailing ..' => ['dir1/dir2/..', 'dir1'],
3115
            'two levels with trailing ../' => ['dir1/dir2/../', 'dir1/'],
3116
            'leading ../ without trailing /' => ['../dir1', '../dir1'],
3117
            'leading ../ with trailing /' => ['../dir1/', '../dir1/'],
3118
            'leading ../ and inside path' => ['../dir1/dir2/../dir3/', '../dir1/dir3/'],
3119
            'one times ../ in relative directory' => ['dir1/../dir2/', 'dir2/'],
3120
            'one times ../ in absolute directory' => ['/dir1/../dir2/', '/dir2/'],
3121
            'one times ../ in relative path' => ['dir1/../dir2/script.php', 'dir2/script.php'],
3122
            'one times ../ in absolute path' => ['/dir1/../dir2/script.php', '/dir2/script.php'],
3123
            'consecutive ../' => ['dir1/dir2/dir3/../../../dir4', 'dir4'],
3124
            'distributed ../ with trailing /' => ['dir1/../dir2/dir3/../', 'dir2/'],
3125
            'distributed ../ without trailing /' => ['dir1/../dir2/dir3/..', 'dir2'],
3126
            'multiple distributed and consecutive ../ together' => ['dir1/dir2/dir3/dir4/../../dir5/dir6/dir7/../dir8/', 'dir1/dir2/dir5/dir6/dir8/'],
3127
            'dirname with leading ..' => ['dir1/..dir2/dir3/', 'dir1/..dir2/dir3/'],
3128
            'dirname with trailing ..' => ['dir1/dir2../dir3/', 'dir1/dir2../dir3/'],
3129
            'more times upwards than downwards in directory' => ['dir1/../../', '../'],
3130
            'more times upwards than downwards in path' => ['dir1/../../script.php', '../script.php'],
3131
        ];
3132
    }
3133
3134
    /**
3135
     * @test
3136
     * @dataProvider resolveBackPathDataProvider
3137
     * @param string $input the input for resolveBackPath
3138
     * @param string $expectedValue Expected return value from resolveBackPath
3139
     */
3140
    public function resolveBackPathWithDataProvider(string $input, string $expectedValue): void
3141
    {
3142
        self::assertEquals($expectedValue, GeneralUtility::resolveBackPath($input));
3143
    }
3144
3145
    /////////////////////////////////////////////////////////////////////////////////////
3146
    // Tests concerning makeInstance, setSingletonInstance, addInstance, purgeInstances
3147
    /////////////////////////////////////////////////////////////////////////////////////
3148
    /**
3149
     * @test
3150
     */
3151
    public function makeInstanceWithEmptyClassNameThrowsException(): void
3152
    {
3153
        $this->expectException(\InvalidArgumentException::class);
3154
        $this->expectExceptionCode(1288965219);
3155
3156
        GeneralUtility::makeInstance('');
3157
    }
3158
3159
    /**
3160
     * @test
3161
     */
3162
    public function makeInstanceWithNullClassNameThrowsException(): void
3163
    {
3164
        $this->expectException(\InvalidArgumentException::class);
3165
        $this->expectExceptionCode(1288965219);
3166
3167
        GeneralUtility::makeInstance(null);
3168
    }
3169
3170
    /**
3171
     * @test
3172
     */
3173
    public function makeInstanceWithZeroStringClassNameThrowsException(): void
3174
    {
3175
        $this->expectException(\InvalidArgumentException::class);
3176
        $this->expectExceptionCode(1288965219);
3177
3178
        GeneralUtility::makeInstance(0);
3179
    }
3180
3181
    /**
3182
     * @test
3183
     */
3184
    public function makeInstanceWithEmptyArrayThrowsException(): void
3185
    {
3186
        $this->expectException(\InvalidArgumentException::class);
3187
        $this->expectExceptionCode(1288965219);
3188
3189
        GeneralUtility::makeInstance([]);
3190
    }
3191
3192
    /**
3193
     * @test
3194
     */
3195
    public function makeInstanceWithNonEmptyArrayThrowsException(): void
3196
    {
3197
        $this->expectException(\InvalidArgumentException::class);
3198
        $this->expectExceptionCode(1288965219);
3199
3200
        GeneralUtility::makeInstance(['foo']);
3201
    }
3202
3203
    /**
3204
     * @test
3205
     */
3206
    public function makeInstanceWithBeginningSlashInClassNameThrowsException(): void
3207
    {
3208
        $this->expectException(\InvalidArgumentException::class);
3209
        $this->expectExceptionCode(1420281366);
3210
3211
        GeneralUtility::makeInstance('\\TYPO3\\CMS\\Backend\\Controller\\BackendController');
3212
    }
3213
3214
    /**
3215
     * @test
3216
     */
3217
    public function makeInstanceReturnsClassInstance(): void
3218
    {
3219
        $className = get_class($this->getMockBuilder('foo')->getMock());
3220
        self::assertInstanceOf($className, GeneralUtility::makeInstance($className));
3221
    }
3222
3223
    /**
3224
     * @test
3225
     */
3226
    public function makeInstancePassesParametersToConstructor(): void
3227
    {
3228
        $instance = GeneralUtility::makeInstance(TwoParametersConstructorFixture::class, 'one parameter', 'another parameter');
3229
        self::assertEquals('one parameter', $instance->constructorParameter1, 'The first constructor parameter has not been set.');
3230
        self::assertEquals('another parameter', $instance->constructorParameter2, 'The second constructor parameter has not been set.');
3231
    }
3232
3233
    /**
3234
     * @test
3235
     */
3236
    public function makeInstanceInstanciatesConfiguredImplementation(): void
3237
    {
3238
        GeneralUtilityFixture::resetFinalClassNameCache();
3239
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][OriginalClassFixture::class] = ['className' => ReplacementClassFixture::class];
3240
        self::assertInstanceOf(ReplacementClassFixture::class, GeneralUtility::makeInstance(OriginalClassFixture::class));
3241
    }
3242
3243
    /**
3244
     * @test
3245
     */
3246
    public function makeInstanceResolvesConfiguredImplementationsRecursively(): void
3247
    {
3248
        GeneralUtilityFixture::resetFinalClassNameCache();
3249
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][OriginalClassFixture::class] = ['className' => ReplacementClassFixture::class];
3250
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][ReplacementClassFixture::class] = ['className' => OtherReplacementClassFixture::class];
3251
        self::assertInstanceOf(OtherReplacementClassFixture::class, GeneralUtility::makeInstance(OriginalClassFixture::class));
3252
    }
3253
3254
    /**
3255
     * @test
3256
     */
3257
    public function makeInstanceCalledTwoTimesForNonSingletonClassReturnsDifferentInstances(): void
3258
    {
3259
        $className = get_class($this->getMockBuilder('foo')->getMock());
3260
        self::assertNotSame(GeneralUtility::makeInstance($className), GeneralUtility::makeInstance($className));
3261
    }
3262
3263
    /**
3264
     * @test
3265
     */
3266
    public function makeInstanceCalledTwoTimesForSingletonClassReturnsSameInstance(): void
3267
    {
3268
        $className = get_class($this->createMock(SingletonInterface::class));
3269
        self::assertSame(GeneralUtility::makeInstance($className), GeneralUtility::makeInstance($className));
3270
    }
3271
3272
    /**
3273
     * @test
3274
     */
3275
    public function makeInstanceCalledTwoTimesForSingletonClassWithPurgeInstancesInbetweenReturnsDifferentInstances(): void
3276
    {
3277
        $className = get_class($this->createMock(SingletonInterface::class));
3278
        $instance = GeneralUtility::makeInstance($className);
3279
        GeneralUtility::purgeInstances();
3280
        self::assertNotSame($instance, GeneralUtility::makeInstance($className));
3281
    }
3282
3283
    /**
3284
     * @test
3285
     */
3286
    public function makeInstanceInjectsLogger(): void
3287
    {
3288
        $instance = GeneralUtility::makeInstance(GeneralUtilityMakeInstanceInjectLoggerFixture::class);
3289
        self::assertInstanceOf(LoggerInterface::class, $instance->getLogger());
3290
    }
3291
3292
    /**
3293
     * @test
3294
     */
3295
    public function setSingletonInstanceForEmptyClassNameThrowsException(): void
3296
    {
3297
        $this->expectException(\InvalidArgumentException::class);
3298
        $this->expectExceptionCode(1288967479);
3299
3300
        $instance = $this->createMock(SingletonInterface::class);
3301
        GeneralUtility::setSingletonInstance('', $instance);
3302
    }
3303
3304
    /**
3305
     * @test
3306
     */
3307
    public function setSingletonInstanceForClassThatIsNoSubclassOfProvidedClassThrowsException(): void
3308
    {
3309
        $this->expectException(\InvalidArgumentException::class);
3310
        $this->expectExceptionCode(1288967686);
3311
3312
        $instance = $this->getMockBuilder(SingletonInterface::class)
3313
            ->addMethods(['foo'])
3314
            ->getMock();
3315
        $singletonClassName = get_class($this->createMock(SingletonInterface::class));
3316
        GeneralUtility::setSingletonInstance($singletonClassName, $instance);
3317
    }
3318
3319
    /**
3320
     * @test
3321
     */
3322
    public function setSingletonInstanceMakesMakeInstanceReturnThatInstance(): void
3323
    {
3324
        $instance = $this->createMock(SingletonInterface::class);
3325
        $singletonClassName = get_class($instance);
3326
        GeneralUtility::setSingletonInstance($singletonClassName, $instance);
3327
        self::assertSame($instance, GeneralUtility::makeInstance($singletonClassName));
3328
    }
3329
3330
    /**
3331
     * @test
3332
     */
3333
    public function setSingletonInstanceCalledTwoTimesMakesMakeInstanceReturnLastSetInstance(): void
3334
    {
3335
        $instance1 = $this->createMock(SingletonInterface::class);
3336
        $singletonClassName = get_class($instance1);
3337
        $instance2 = new $singletonClassName();
3338
        GeneralUtility::setSingletonInstance($singletonClassName, $instance1);
3339
        GeneralUtility::setSingletonInstance($singletonClassName, $instance2);
3340
        self::assertSame($instance2, GeneralUtility::makeInstance($singletonClassName));
3341
    }
3342
3343
    /**
3344
     * @test
3345
     */
3346
    public function getSingletonInstancesContainsPreviouslySetSingletonInstance(): void
3347
    {
3348
        $instance = $this->createMock(SingletonInterface::class);
3349
        $instanceClassName = get_class($instance);
3350
        GeneralUtility::setSingletonInstance($instanceClassName, $instance);
3351
        $registeredSingletonInstances = GeneralUtility::getSingletonInstances();
3352
        self::assertArrayHasKey($instanceClassName, $registeredSingletonInstances);
3353
        self::assertSame($registeredSingletonInstances[$instanceClassName], $instance);
3354
    }
3355
3356
    /**
3357
     * @test
3358
     */
3359
    public function setSingletonInstanceReturnsFinalClassNameWithOverriddenClass(): void
3360
    {
3361
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][SingletonClassFixture::class]['className'] = ExtendedSingletonClassFixture::class;
3362
        $anotherInstance = new ExtendedSingletonClassFixture();
3363
        GeneralUtility::makeInstance(SingletonClassFixture::class);
3364
        GeneralUtility::setSingletonInstance(SingletonClassFixture::class, $anotherInstance);
3365
        $result = GeneralUtility::makeInstance(SingletonClassFixture::class);
3366
        self::assertSame($anotherInstance, $result);
3367
        self::assertEquals(ExtendedSingletonClassFixture::class, get_class($anotherInstance));
3368
    }
3369
3370
    /**
3371
     * @test
3372
     */
3373
    public function resetSingletonInstancesResetsPreviouslySetInstance(): void
3374
    {
3375
        $instance = $this->createMock(SingletonInterface::class);
3376
        $instanceClassName = get_class($instance);
3377
        GeneralUtility::setSingletonInstance($instanceClassName, $instance);
3378
        GeneralUtility::resetSingletonInstances([]);
3379
        $registeredSingletonInstances = GeneralUtility::getSingletonInstances();
3380
        self::assertArrayNotHasKey($instanceClassName, $registeredSingletonInstances);
3381
    }
3382
3383
    /**
3384
     * @test
3385
     */
3386
    public function resetSingletonInstancesSetsGivenInstance(): void
3387
    {
3388
        $instance = $this->createMock(SingletonInterface::class);
3389
        $instanceClassName = get_class($instance);
3390
        GeneralUtility::resetSingletonInstances(
3391
            [$instanceClassName => $instance]
3392
        );
3393
        $registeredSingletonInstances = GeneralUtility::getSingletonInstances();
3394
        self::assertArrayHasKey($instanceClassName, $registeredSingletonInstances);
3395
        self::assertSame($registeredSingletonInstances[$instanceClassName], $instance);
3396
    }
3397
3398
    /**
3399
     * @test
3400
     */
3401
    public function addInstanceForEmptyClassNameThrowsException(): void
3402
    {
3403
        $this->expectException(\InvalidArgumentException::class);
3404
        $this->expectExceptionCode(1288967479);
3405
3406
        $instance = $this->getMockBuilder('foo')->getMock();
3407
        GeneralUtility::addInstance('', $instance);
3408
    }
3409
3410
    /**
3411
     * @test
3412
     */
3413
    public function addInstanceForClassThatIsNoSubclassOfProvidedClassThrowsException(): void
3414
    {
3415
        $this->expectException(\InvalidArgumentException::class);
3416
        $this->expectExceptionCode(1288967686);
3417
3418
        $instance = $this->getMockBuilder(\stdClass::class)
3419
            ->addMethods(['bar'])
3420
            ->getMock();
3421
        $singletonClassName = get_class($this->createMock(\stdClass::class));
3422
        GeneralUtility::addInstance($singletonClassName, $instance);
3423
    }
3424
3425
    /**
3426
     * @test
3427
     */
3428
    public function addInstanceWithSingletonInstanceThrowsException(): void
3429
    {
3430
        $this->expectException(\InvalidArgumentException::class);
3431
        $this->expectExceptionCode(1288969325);
3432
3433
        $instance = $this->createMock(SingletonInterface::class);
3434
        GeneralUtility::addInstance(get_class($instance), $instance);
3435
    }
3436
3437
    /**
3438
     * @test
3439
     */
3440
    public function addInstanceMakesMakeInstanceReturnThatInstance(): void
3441
    {
3442
        $instance = $this->createMock('stdClass');
3443
        $className = get_class($instance);
3444
        GeneralUtility::addInstance($className, $instance);
3445
        self::assertSame($instance, GeneralUtility::makeInstance($className));
3446
    }
3447
3448
    /**
3449
     * @test
3450
     */
3451
    public function makeInstanceCalledTwoTimesAfterAddInstanceReturnTwoDifferentInstances(): void
3452
    {
3453
        $instance = $this->createMock('stdClass');
3454
        $className = get_class($instance);
3455
        GeneralUtility::addInstance($className, $instance);
3456
        self::assertNotSame(GeneralUtility::makeInstance($className), GeneralUtility::makeInstance($className));
3457
    }
3458
3459
    /**
3460
     * @test
3461
     */
3462
    public function addInstanceCalledTwoTimesMakesMakeInstanceReturnBothInstancesInAddingOrder(): void
3463
    {
3464
        $instance1 = $this->createMock('stdClass');
3465
        $className = get_class($instance1);
3466
        GeneralUtility::addInstance($className, $instance1);
3467
        $instance2 = new $className();
3468
        GeneralUtility::addInstance($className, $instance2);
3469
        self::assertSame($instance1, GeneralUtility::makeInstance($className), 'The first returned instance does not match the first added instance.');
3470
        self::assertSame($instance2, GeneralUtility::makeInstance($className), 'The second returned instance does not match the second added instance.');
3471
    }
3472
3473
    /**
3474
     * @test
3475
     */
3476
    public function purgeInstancesDropsAddedInstance(): void
3477
    {
3478
        $instance = $this->createMock('stdClass');
3479
        $className = get_class($instance);
3480
        GeneralUtility::addInstance($className, $instance);
3481
        GeneralUtility::purgeInstances();
3482
        self::assertNotSame($instance, GeneralUtility::makeInstance($className));
3483
    }
3484
3485
    /**
3486
     * @return array
3487
     */
3488
    public function getFileAbsFileNameDataProvider(): array
3489
    {
3490
        return [
3491
            'relative path is prefixed with public path' => [
3492
                'fileadmin/foo.txt',
3493
                Environment::getPublicPath() . '/fileadmin/foo.txt',
3494
            ],
3495
            'relative path, referencing current directory is prefixed with public path' => [
3496
                './fileadmin/foo.txt',
3497
                Environment::getPublicPath() . '/./fileadmin/foo.txt',
3498
            ],
3499
            'relative paths with back paths are not allowed and returned empty' => [
3500
                '../fileadmin/foo.txt',
3501
                '',
3502
            ],
3503
            'absolute paths with back paths are not allowed and returned empty' => [
3504
                Environment::getPublicPath() . '/../sysext/core/Resources/Public/Icons/Extension.png',
3505
                '',
3506
            ],
3507
            'allowed absolute paths are returned as is' => [
3508
                Environment::getPublicPath() . '/fileadmin/foo.txt',
3509
                Environment::getPublicPath() . '/fileadmin/foo.txt',
3510
            ],
3511
            'disallowed absolute paths are returned empty' => [
3512
                '/somewhere/fileadmin/foo.txt',
3513
                '',
3514
            ],
3515
            'EXT paths are resolved to absolute paths' => [
3516
                'EXT:foo/Resources/Private/Templates/Home.html',
3517
                '/path/to/foo/Resources/Private/Templates/Home.html',
3518
            ],
3519
        ];
3520
    }
3521
3522
    /**
3523
     * @param string $path
3524
     * @param string $expected
3525
     * @test
3526
     * @dataProvider getFileAbsFileNameDataProvider
3527
     */
3528
    public function getFileAbsFileNameReturnsCorrectValues(string $path, string $expected): void
3529
    {
3530
        // build the dummy package "foo" for use in ExtensionManagementUtility::extPath('foo');
3531
        $package = $this->getMockBuilder(Package::class)
3532
            ->disableOriginalConstructor()
3533
            ->onlyMethods(['getPackagePath'])
3534
            ->getMock();
3535
        /** @var PackageManager|\PHPUnit\Framework\MockObject\MockObject $packageManager */
3536
        $packageManager = $this->getMockBuilder(PackageManager::class)
3537
            ->onlyMethods(['isPackageActive', 'getPackage', 'getActivePackages'])
3538
            ->disableOriginalConstructor()
3539
            ->getMock();
3540
        $package
3541
            ->method('getPackagePath')
3542
            ->willReturn('/path/to/foo/');
3543
        $packageManager
3544
            ->method('getActivePackages')
3545
            ->willReturn(['foo' => $package]);
3546
        $packageManager
3547
            ->method('isPackageActive')
3548
            ->with(self::equalTo('foo'))
3549
            ->willReturn(true);
3550
        $packageManager
3551
            ->method('getPackage')
3552
            ->with('foo')
3553
            ->willReturn($package);
3554
        ExtensionManagementUtility::setPackageManager($packageManager);
3555
3556
        $result = GeneralUtility::getFileAbsFileName($path);
3557
        self::assertEquals($expected, $result);
3558
    }
3559
3560
    /**
3561
     * Data provider for validPathStrDetectsInvalidCharacters.
3562
     *
3563
     * @return array
3564
     */
3565
    public function validPathStrInvalidCharactersDataProvider(): array
3566
    {
3567
        $data = [
3568
            'double slash in path' => ['path//path'],
3569
            'backslash in path' => ['path\\path'],
3570
            'directory up in path' => ['path/../path'],
3571
            'directory up at the beginning' => ['../path'],
3572
            'NUL character in path' => ['path' . "\0" . 'path'],
3573
            'BS character in path' => ['path' . chr(8) . 'path'],
3574
            'invalid UTF-8-sequence' => ["\xc0" . 'path/path'],
3575
            'Could be overlong NUL in some UTF-8 implementations, invalid in RFC3629' => ["\xc0\x80" . 'path/path'],
3576
        ];
3577
3578
        // Mixing with regular utf-8
3579
        $utf8Characters = 'Ссылка/';
3580
        foreach ($data as $key => $value) {
3581
            $data[$key . ' with UTF-8 characters prepended'] = [$utf8Characters . $value[0]];
3582
            $data[$key . ' with UTF-8 characters appended'] = [$value[0] . $utf8Characters];
3583
        }
3584
3585
        // Encoding with UTF-16
3586
        foreach ($data as $key => $value) {
3587
            $data[$key . ' encoded with UTF-16'] = [mb_convert_encoding($value[0], 'UTF-16')];
3588
        }
3589
3590
        return $data;
3591
    }
3592
3593
    /**
3594
     * Tests whether invalid characters are detected.
3595
     *
3596
     * @param string $path
3597
     * @dataProvider validPathStrInvalidCharactersDataProvider
3598
     * @test
3599
     */
3600
    public function validPathStrDetectsInvalidCharacters(string $path): void
3601
    {
3602
        self::assertFalse(GeneralUtility::validPathStr($path));
3603
    }
3604
3605
    /**
3606
     * Data provider for positive values within validPathStr()
3607
     */
3608
    public function validPathStrDataProvider(): array
3609
    {
3610
        $data = [
3611
            'normal ascii path' => ['fileadmin/templates/myfile..xml'],
3612
            'special character' => ['fileadmin/templates/Ссылка (fce).xml'],
3613
        ];
3614
3615
        return $data;
3616
    }
3617
3618
    /**
3619
     * Tests whether Unicode characters are recognized as valid file name characters.
3620
     *
3621
     * @dataProvider validPathStrDataProvider
3622
     * @test
3623
     */
3624
    public function validPathStrWorksWithUnicodeFileNames($path): void
3625
    {
3626
        self::assertTrue(GeneralUtility::validPathStr($path));
3627
    }
3628
3629
    /////////////////////////////////////////////////////////////////////////////////////
3630
    // Tests concerning copyDirectory
3631
    /////////////////////////////////////////////////////////////////////////////////////
3632
3633
    /**
3634
     * @test
3635
     */
3636
    public function copyDirectoryCopiesFilesAndDirectoriesWithRelativePaths(): void
3637
    {
3638
        $sourceDirectory = 'typo3temp/var/tests/' . StringUtility::getUniqueId('test_') . '/';
3639
        $absoluteSourceDirectory = Environment::getPublicPath() . '/' . $sourceDirectory;
3640
        $this->testFilesToDelete[] = $absoluteSourceDirectory;
3641
        GeneralUtility::mkdir($absoluteSourceDirectory);
3642
3643
        $targetDirectory = 'typo3temp/var/tests/' . StringUtility::getUniqueId('test_') . '/';
3644
        $absoluteTargetDirectory = Environment::getPublicPath() . '/' . $targetDirectory;
3645
        $this->testFilesToDelete[] = $absoluteTargetDirectory;
3646
3647
        GeneralUtility::writeFileToTypo3tempDir($absoluteSourceDirectory . 'file', '42');
3648
        GeneralUtility::mkdir($absoluteSourceDirectory . 'foo');
3649
        GeneralUtility::writeFileToTypo3tempDir($absoluteSourceDirectory . 'foo/file', '42');
3650
3651
        GeneralUtility::copyDirectory($sourceDirectory, $targetDirectory);
3652
3653
        self::assertFileExists($absoluteTargetDirectory . 'file');
3654
        self::assertFileExists($absoluteTargetDirectory . 'foo/file');
3655
    }
3656
3657
    /**
3658
     * @test
3659
     */
3660
    public function copyDirectoryCopiesFilesAndDirectoriesWithAbsolutePaths(): void
3661
    {
3662
        $sourceDirectory = 'typo3temp/var/tests/' . StringUtility::getUniqueId('test_') . '/';
3663
        $absoluteSourceDirectory = Environment::getPublicPath() . '/' . $sourceDirectory;
3664
        $this->testFilesToDelete[] = $absoluteSourceDirectory;
3665
        GeneralUtility::mkdir($absoluteSourceDirectory);
3666
3667
        $targetDirectory = 'typo3temp/var/tests/' . StringUtility::getUniqueId('test_') . '/';
3668
        $absoluteTargetDirectory = Environment::getPublicPath() . '/' . $targetDirectory;
3669
        $this->testFilesToDelete[] = $absoluteTargetDirectory;
3670
3671
        GeneralUtility::writeFileToTypo3tempDir($absoluteSourceDirectory . 'file', '42');
3672
        GeneralUtility::mkdir($absoluteSourceDirectory . 'foo');
3673
        GeneralUtility::writeFileToTypo3tempDir($absoluteSourceDirectory . 'foo/file', '42');
3674
3675
        GeneralUtility::copyDirectory($absoluteSourceDirectory, $absoluteTargetDirectory);
3676
3677
        self::assertFileExists($absoluteTargetDirectory . 'file');
3678
        self::assertFileExists($absoluteTargetDirectory . 'foo/file');
3679
    }
3680
3681
    /////////////////////////////////////////////////////////////////////////////////////
3682
    // Tests concerning deprecation log
3683
    /////////////////////////////////////////////////////////////////////////////////////
3684
3685
    ///////////////////////////////////////////////////
3686
    // Tests concerning callUserFunction
3687
    ///////////////////////////////////////////////////
3688
3689
    /**
3690
     * @test
3691
     * @dataProvider callUserFunctionInvalidParameterDataprovider
3692
     * @param string $functionName
3693
     * @param int $expectedException
3694
     */
3695
    public function callUserFunctionWillThrowExceptionForInvalidParameters(string $functionName, int $expectedException): void
3696
    {
3697
        $this->expectException(\InvalidArgumentException::class);
3698
        $this->expectExceptionCode($expectedException);
3699
3700
        $inputData = ['foo' => 'bar'];
3701
        GeneralUtility::callUserFunction($functionName, $inputData, $this);
3702
    }
3703
3704
    /**
3705
     * Data provider for callUserFunctionInvalidParameterDataprovider and
3706
     * callUserFunctionWillThrowExceptionForInvalidParameters.
3707
     *
3708
     * @return array
3709
     */
3710
    public function callUserFunctionInvalidParameterDataprovider(): array
3711
    {
3712
        return [
3713
            'Function is not prefixed' => [self::class . '->calledUserFunction', 1294585865],
3714
            'Class doesn\'t exists' => ['t3lib_divTest21345->user_calledUserFunction', 1294585866],
3715
            'No method name' => [self::class, 1294585867],
3716
            'No class name' => ['->user_calledUserFunction', 1294585866],
3717
            'No function name' => ['', 1294585867],
3718
        ];
3719
    }
3720
3721
    /**
3722
     * Above tests already showed that the prefix is checked properly,
3723
     * therefore this test skips the prefix and enables to inline the instantly
3724
     * created function (who's name doesn't have a prefix).
3725
     *
3726
     * @test
3727
     */
3728
    public function callUserFunctionCanCallFunction(): void
3729
    {
3730
        $inputData = ['foo' => 'bar'];
3731
        $result = GeneralUtility::callUserFunction(static function () {
0 ignored issues
show
Bug introduced by
function(...) { /* ... */ } of type callable is incompatible with the type string expected by parameter $funcName of TYPO3\CMS\Core\Utility\G...ity::callUserFunction(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3731
        $result = GeneralUtility::callUserFunction(/** @scrutinizer ignore-type */ static function () {
Loading history...
3732
            return 'Worked fine';
3733
        }, $inputData, $this, '');
0 ignored issues
show
Unused Code introduced by
The call to TYPO3\CMS\Core\Utility\G...ity::callUserFunction() has too many arguments starting with ''. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

3733
        /** @scrutinizer ignore-call */ 
3734
        $result = GeneralUtility::callUserFunction(static function () {

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
3734
        self::assertEquals('Worked fine', $result);
3735
    }
3736
3737
    /**
3738
     * @test
3739
     */
3740
    public function callUserFunctionCanCallMethod(): void
3741
    {
3742
        $inputData = ['foo' => 'bar'];
3743
        $result = GeneralUtility::callUserFunction(self::class . '->user_calledUserFunction', $inputData, $this);
3744
        self::assertEquals('Worked fine', $result);
3745
    }
3746
3747
    /**
3748
     * @return string
3749
     */
3750
    public function user_calledUserFunction(): string
3751
    {
3752
        return 'Worked fine';
3753
    }
3754
3755
    /**
3756
     * @test
3757
     */
3758
    public function callUserFunctionAcceptsClosures(): void
3759
    {
3760
        $inputData = ['foo' => 'bar'];
3761
        $closure = static function ($parameters, $reference) use ($inputData) {
3762
            $reference->assertEquals($inputData, $parameters, 'Passed data doesn\'t match expected output');
3763
            return 'Worked fine';
3764
        };
3765
        self::assertEquals('Worked fine', GeneralUtility::callUserFunction($closure, $inputData, $this));
0 ignored issues
show
Bug introduced by
$closure of type callable is incompatible with the type string expected by parameter $funcName of TYPO3\CMS\Core\Utility\G...ity::callUserFunction(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3765
        self::assertEquals('Worked fine', GeneralUtility::callUserFunction(/** @scrutinizer ignore-type */ $closure, $inputData, $this));
Loading history...
3766
    }
3767
3768
    /**
3769
     * @test
3770
     */
3771
    public function callUserFunctionTrimsSpaces(): void
3772
    {
3773
        $inputData = ['foo' => 'bar'];
3774
        $result = GeneralUtility::callUserFunction("\t" . self::class . '->user_calledUserFunction ', $inputData, $this);
3775
        self::assertEquals('Worked fine', $result);
3776
    }
3777
3778
    /**
3779
     * @test
3780
     */
3781
    public function getAllFilesAndFoldersInPathReturnsArrayWithMd5Keys(): void
3782
    {
3783
        $directory = Environment::getVarPath() . '/tests/' . StringUtility::getUniqueId('directory_');
3784
        mkdir($directory);
3785
        $filesAndDirectories = GeneralUtility::getAllFilesAndFoldersInPath([], $directory, '', true);
3786
        $check = true;
3787
        foreach ($filesAndDirectories as $md5 => $path) {
3788
            if (!preg_match('/^[a-f0-9]{32}$/', $md5)) {
3789
                $check = false;
3790
            }
3791
        }
3792
        GeneralUtility::rmdir($directory);
3793
        self::assertTrue($check);
3794
    }
3795
3796
    /**
3797
     * If the element is not empty, its contents might be treated as "something" (instead of "nothing")
3798
     * e.g. by Fluid view helpers, which is why we want to avoid that.
3799
     *
3800
     * @test
3801
     */
3802
    public function array2xmlConvertsEmptyArraysToElementWithoutContent(): void
3803
    {
3804
        $input = [
3805
            'el' => [],
3806
        ];
3807
3808
        $output = GeneralUtility::array2xml($input);
3809
3810
        self::assertEquals('<phparray>
3811
	<el type="array"></el>
3812
</phparray>', $output);
3813
    }
3814
3815
    /**
3816
     * @test
3817
     */
3818
    public function xml2arrayUsesCache(): void
3819
    {
3820
        $cacheManagerProphecy = $this->prophesize(CacheManager::class);
3821
        $cacheProphecy = $this->prophesize(FrontendInterface::class);
3822
        $cacheManagerProphecy->getCache('runtime')->willReturn($cacheProphecy->reveal());
3823
        $cacheProphecy->get('generalUtilityXml2Array')->shouldBeCalled()->willReturn(false);
3824
        $cacheProphecy->set('generalUtilityXml2Array', Argument::cetera())->shouldBeCalled();
3825
        GeneralUtility::setSingletonInstance(CacheManager::class, $cacheManagerProphecy->reveal());
3826
        GeneralUtility::xml2array('<?xml version="1.0" encoding="utf-8" standalone="yes"?>', 'T3:');
3827
    }
3828
3829
    /**
3830
     * @return string[][]
3831
     */
3832
    public function xml2arrayProcessHandlesWhitespacesDataProvider(): array
3833
    {
3834
        $headerVariants = [
3835
            'utf-8' => '<?xml version="1.0" encoding="utf-8" standalone="yes"?>',
3836
            'UTF-8' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
3837
            'no-encoding' => '<?xml version="1.0" standalone="yes"?>',
3838
            'iso-8859-1' => '<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>',
3839
            'ISO-8859-1' => '<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>',
3840
        ];
3841
        $data = [];
3842
        foreach ($headerVariants as $identifier => $headerVariant) {
3843
            $data += [
3844
                'inputWithoutWhitespaces-' . $identifier => [
3845
                    $headerVariant . '<T3FlexForms>
3846
                        <data>
3847
                            <field index="settings.persistenceIdentifier">
3848
                                <value index="vDEF">egon</value>
3849
                            </field>
3850
                        </data>
3851
                    </T3FlexForms>',
3852
                ],
3853
                'inputWithPrecedingWhitespaces-' . $identifier => [
3854
                    CR . ' ' . $headerVariant . '<T3FlexForms>
3855
                        <data>
3856
                            <field index="settings.persistenceIdentifier">
3857
                                <value index="vDEF">egon</value>
3858
                            </field>
3859
                        </data>
3860
                    </T3FlexForms>',
3861
                ],
3862
                'inputWithTrailingWhitespaces-' . $identifier => [
3863
                    $headerVariant . '<T3FlexForms>
3864
                        <data>
3865
                            <field index="settings.persistenceIdentifier">
3866
                                <value index="vDEF">egon</value>
3867
                            </field>
3868
                        </data>
3869
                    </T3FlexForms>' . CR . ' ',
3870
                ],
3871
                'inputWithPrecedingAndTrailingWhitespaces-' . $identifier => [
3872
                    CR . ' ' . $headerVariant . '<T3FlexForms>
3873
                        <data>
3874
                            <field index="settings.persistenceIdentifier">
3875
                                <value index="vDEF">egon</value>
3876
                            </field>
3877
                        </data>
3878
                    </T3FlexForms>' . CR . ' ',
3879
                ],
3880
            ];
3881
        }
3882
        return $data;
3883
    }
3884
3885
    /**
3886
     * @test
3887
     * @dataProvider xml2arrayProcessHandlesWhitespacesDataProvider
3888
     */
3889
    public function xml2arrayProcessHandlesWhitespaces(string $input): void
3890
    {
3891
        $expected = [
3892
            'data' => [
3893
                'settings.persistenceIdentifier' => [
3894
                    'vDEF' => 'egon',
3895
                ],
3896
            ],
3897
        ];
3898
        self::assertSame($expected, GeneralUtility::xml2arrayProcess($input));
3899
    }
3900
3901
    /**
3902
     * @return string[][]
3903
     */
3904
    public function xml2arrayProcessHandlesTagNamespacesDataProvider(): array
3905
    {
3906
        return [
3907
            'inputWithNameSpaceOnRootLevel' => [
3908
                '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
3909
                <T3:T3FlexForms>
3910
                    <data>
3911
                        <field index="settings.persistenceIdentifier">
3912
                            <value index="vDEF">egon</value>
3913
                        </field>
3914
                    </data>
3915
                </T3:T3FlexForms>',
3916
            ],
3917
            'inputWithNameSpaceOnNonRootLevel' => [
3918
                '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
3919
                <T3FlexForms>
3920
                    <data>
3921
                        <T3:field index="settings.persistenceIdentifier">
3922
                            <value index="vDEF">egon</value>
3923
                        </T3:field>
3924
                    </data>
3925
                </T3FlexForms>',
3926
            ],
3927
            'inputWithNameSpaceOnRootAndNonRootLevel' => [
3928
                '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
3929
                <T3:T3FlexForms>
3930
                    <data>
3931
                        <T3:field index="settings.persistenceIdentifier">
3932
                            <value index="vDEF">egon</value>
3933
                        </T3:field>
3934
                    </data>
3935
                </T3:T3FlexForms>',
3936
            ],
3937
        ];
3938
    }
3939
3940
    /**
3941
     * @test
3942
     * @dataProvider xml2arrayProcessHandlesTagNamespacesDataProvider
3943
     */
3944
    public function xml2arrayProcessHandlesTagNamespaces(string $input): void
3945
    {
3946
        $expected = [
3947
            'data' => [
3948
                'settings.persistenceIdentifier' => [
3949
                    'vDEF' => 'egon',
3950
                ],
3951
            ],
3952
        ];
3953
        self::assertSame($expected, GeneralUtility::xml2arrayProcess($input, 'T3:'));
3954
    }
3955
3956
    /**
3957
     * @return array[]
3958
     */
3959
    public function xml2arrayProcessHandlesDocumentTagDataProvider(): array
3960
    {
3961
        return [
3962
            'input' => [
3963
                '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
3964
                <T3FlexForms>
3965
                    <data>
3966
                        <field index="settings.persistenceIdentifier">
3967
                            <value index="vDEF">egon</value>
3968
                        </field>
3969
                    </data>
3970
                </T3FlexForms>',
3971
                'T3FlexForms',
3972
            ],
3973
            'input-with-root-namespace' => [
3974
                '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
3975
                <T3:T3FlexForms>
3976
                    <data>
3977
                        <field index="settings.persistenceIdentifier">
3978
                            <value index="vDEF">egon</value>
3979
                        </field>
3980
                    </data>
3981
                </T3:T3FlexForms>',
3982
                'T3:T3FlexForms',
3983
            ],
3984
            'input-with-namespace' => [
3985
                '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
3986
                <T3FlexForms>
3987
                    <data>
3988
                        <T3:field index="settings.persistenceIdentifier">
3989
                            <value index="vDEF">egon</value>
3990
                        </T3:field>
3991
                    </data>
3992
                </T3FlexForms>',
3993
                'T3FlexForms',
3994
            ],
3995
        ];
3996
    }
3997
3998
    /**
3999
     * @test
4000
     * @dataProvider xml2arrayProcessHandlesDocumentTagDataProvider
4001
     */
4002
    public function xml2arrayProcessHandlesDocumentTag(string $input, string $docTag): void
4003
    {
4004
        $expected = [
4005
            'data' => [
4006
                'settings.persistenceIdentifier' => [
4007
                    'vDEF' => 'egon',
4008
                ],
4009
            ],
4010
            '_DOCUMENT_TAG' => $docTag,
4011
        ];
4012
        self::assertSame($expected, GeneralUtility::xml2arrayProcess($input, '', true));
4013
    }
4014
4015
    /**
4016
     * @return array[]
4017
     */
4018
    public function xml2ArrayProcessHandlesBigXmlContentDataProvider(): array
4019
    {
4020
        return [
4021
            '1mb' => [
4022
                '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
4023
                <T3:T3FlexForms>
4024
                    <data>
4025
                        <field index="settings.persistenceIdentifier">
4026
                            <value index="vDEF">' . str_repeat('1', 1024 * 1024) . '</value>
4027
                        </field>
4028
                    </data>
4029
                </T3:T3FlexForms>',
4030
                str_repeat('1', 1024 * 1024),
4031
            ],
4032
            '5mb' => [
4033
                '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
4034
                <T3:T3FlexForms>
4035
                    <data>
4036
                        <field index="settings.persistenceIdentifier">
4037
                            <value index="vDEF">' . str_repeat('1', 5 * 1024 * 1024) . '</value>
4038
                        </field>
4039
                    </data>
4040
                </T3:T3FlexForms>',
4041
                str_repeat('1', 5 * 1024 * 1024),
4042
            ],
4043
        ];
4044
    }
4045
4046
    /**
4047
     * @test
4048
     * @dataProvider xml2ArrayProcessHandlesBigXmlContentDataProvider
4049
     * @param string $input
4050
     * @param string $testValue
4051
     */
4052
    public function xml2ArrayProcessHandlesBigXmlContent(string $input, string $testValue): void
4053
    {
4054
        $expected = [
4055
            'data' => [
4056
                'settings.persistenceIdentifier' => [
4057
                    'vDEF' => $testValue,
4058
                ],
4059
            ],
4060
        ];
4061
        self::assertSame($expected, GeneralUtility::xml2arrayProcess($input));
4062
    }
4063
4064
    /**
4065
     * @todo: The parser run into a memory issue with files bigger 10 MB
4066
     * @todo: This special tests documents the issue. If fixed, this test
4067
     * @todo: should become a data set of xml2ArrayHandlesBigXmlFilesDataProvider()
4068
     * @todo: This test does not pass in all environments. It should be evaluated whether this test is really needed or should be removed.
4069
     *
4070
     * @see https://forge.typo3.org/issues/83580
4071
     *
4072
     * @test
4073
     */
4074
    public function xml2ArrayFailsWithXmlContentBiggerThanTenMegabytes(): void
4075
    {
4076
        self::markTestSkipped('This test does not pass in all environments. It should be evaluated whether this test is really needed or should be removed.');
4077
        $cacheManagerProphecy = $this->prophesize(CacheManager::class);
4078
        $cacheProphecy = $this->prophesize(FrontendInterface::class);
4079
        $cacheManagerProphecy->getCache('runtime')->willReturn($cacheProphecy->reveal());
4080
        $cacheProphecy->get('generalUtilityXml2Array')->shouldBeCalled()->willReturn(false);
4081
        $cacheProphecy->set('generalUtilityXml2Array', Argument::cetera())->shouldBeCalled();
4082
        GeneralUtility::setSingletonInstance(CacheManager::class, $cacheManagerProphecy->reveal());
4083
        $input = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
4084
            <T3:T3FlexForms>
4085
                <data>
4086
                    <field index="settings.persistenceIdentifier">
4087
                        <value index="vDEF">' . str_repeat('1', 10 * 1024 * 1024) . '</value>
4088
                    </field>
4089
                </data>
4090
            </T3:T3FlexForms>';
4091
        self::assertStringContainsString('No memory', GeneralUtility::xml2array($input));
4092
    }
4093
4094
    /**
4095
     * @return array[]
4096
     */
4097
    public function xml2ArrayProcessHandlesAttributeTypesDataProvider(): array
4098
    {
4099
        $prefix = '<?xml version="1.0" encoding="utf-8" standalone="yes"?><T3FlexForms><field index="index">';
4100
        $suffix = '</field></T3FlexForms>';
4101
        return [
4102
            'no-type string' => [
4103
                $prefix . '<value index="vDEF">foo bar</value>' . $suffix,
4104
                'foo bar',
4105
            ],
4106
            'no-type integer' => [
4107
                $prefix . '<value index="vDEF">123</value>' . $suffix,
4108
                '123',
4109
            ],
4110
            'no-type double' => [
4111
                $prefix . '<value index="vDEF">1.23</value>' . $suffix,
4112
                '1.23',
4113
            ],
4114
            'integer integer' => [
4115
                $prefix . '<value index="vDEF" type="integer">123</value>' . $suffix,
4116
                123,
4117
            ],
4118
            'integer double' => [
4119
                $prefix . '<value index="vDEF" type="integer">1.23</value>' . $suffix,
4120
                1,
4121
            ],
4122
            'double integer' => [
4123
                $prefix . '<value index="vDEF" type="double">123</value>' . $suffix,
4124
                123.0,
4125
            ],
4126
            'double double' => [
4127
                $prefix . '<value index="vDEF" type="double">1.23</value>' . $suffix,
4128
                1.23,
4129
            ],
4130
            'boolean 0' => [
4131
                $prefix . '<value index="vDEF" type="boolean">0</value>' . $suffix,
4132
                false,
4133
            ],
4134
            'boolean 1' => [
4135
                $prefix . '<value index="vDEF" type="boolean">1</value>' . $suffix,
4136
                true,
4137
            ],
4138
            'boolean true' => [
4139
                $prefix . '<value index="vDEF" type="boolean">true</value>' . $suffix,
4140
                true,
4141
            ],
4142
            'boolean false' => [
4143
                $prefix . '<value index="vDEF" type="boolean">false</value>' . $suffix,
4144
                true, // sic(!)
4145
            ],
4146
            'NULL' => [
4147
                $prefix . '<value index="vDEF" type="NULL"></value>' . $suffix,
4148
                null,
4149
            ],
4150
            'NULL string' => [
4151
                $prefix . '<value index="vDEF" type="NULL">foo bar</value>' . $suffix,
4152
                null,
4153
            ],
4154
            'NULL integer' => [
4155
                $prefix . '<value index="vDEF" type="NULL">123</value>' . $suffix,
4156
                null,
4157
            ],
4158
            'NULL double' => [
4159
                $prefix . '<value index="vDEF" type="NULL">1.23</value>' . $suffix,
4160
                null,
4161
            ],
4162
            'array' => [
4163
                $prefix . '<value index="vDEF" type="array"></value>' . $suffix,
4164
                [],
4165
            ],
4166
        ];
4167
    }
4168
4169
    /**
4170
     * @test
4171
     * @dataProvider xml2ArrayProcessHandlesAttributeTypesDataProvider
4172
     * @param string $input
4173
     * @param $expected
4174
     */
4175
    public function xml2ArrayProcessHandlesAttributeTypes(string $input, $expected): void
4176
    {
4177
        $result = GeneralUtility::xml2arrayProcess($input);
4178
        self::assertSame($expected, $result['index']['vDEF']);
4179
    }
4180
4181
    public function locationHeaderUrlDataProvider(): array
4182
    {
4183
        return [
4184
            'simple relative path' => [
4185
                'foo',
4186
                'foo.bar.test',
4187
                'http://foo.bar.test/foo',
4188
            ],
4189
            'path beginning with slash' => [
4190
                '/foo',
4191
                'foo.bar.test',
4192
                'http://foo.bar.test/foo',
4193
            ],
4194
            'path with full domain and https scheme' => [
4195
                'https://example.com/foo',
4196
                'foo.bar.test',
4197
                'https://example.com/foo',
4198
            ],
4199
            'path with full domain and http scheme' => [
4200
                'http://example.com/foo',
4201
                'foo.bar.test',
4202
                'http://example.com/foo',
4203
            ],
4204
            'path with full domain and relative scheme' => [
4205
                '//example.com/foo',
4206
                'foo.bar.test',
4207
                '//example.com/foo',
4208
            ],
4209
        ];
4210
    }
4211
4212
    /**
4213
     * @test
4214
     * @param string $path
4215
     * @param string $host
4216
     * @param string $expected
4217
     * @dataProvider locationHeaderUrlDataProvider
4218
     * @throws \TYPO3\CMS\Core\Exception
4219
     */
4220
    public function locationHeaderUrl(string $path, string $host, string $expected): void
4221
    {
4222
        Environment::initialize(
4223
            Environment::getContext(),
4224
            true,
4225
            false,
4226
            Environment::getProjectPath(),
4227
            Environment::getPublicPath(),
4228
            Environment::getVarPath(),
4229
            Environment::getConfigPath(),
4230
            Environment::getCurrentScript(),
4231
            Environment::isWindows() ? 'WINDOWS' : 'UNIX'
4232
        );
4233
        $_SERVER['HTTP_HOST'] = $host;
4234
        $_SERVER['SCRIPT_NAME'] = '/index.php';
4235
        $result = GeneralUtility::locationHeaderUrl($path);
4236
        self::assertSame($expected, $result);
4237
    }
4238
}
4239