[SECURITY] Mitigate phar stream wrapper
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Unit / LinkHandling / LegacyLinkNotationConverterTest.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Core\Tests\Unit\LinkHandling;
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 use TYPO3\CMS\Core\LinkHandling\LegacyLinkNotationConverter;
19 use TYPO3\CMS\Core\LinkHandling\LinkService;
20 use TYPO3\CMS\Core\Resource\File;
21 use TYPO3\CMS\Core\Resource\Folder;
22 use TYPO3\CMS\Core\Resource\ResourceFactory;
23 use TYPO3\CMS\Core\Resource\ResourceStorage;
24 use TYPO3\CMS\Core\Utility\MathUtility;
25 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
26
27 class LegacyLinkNotationConverterTest extends UnitTestCase
28 {
29 /**
30 * Data to resolve strings to arrays and vice versa, external, mail, page
31 *
32 * @return array
33 */
34 public function resolveParametersForNonFilesDataProvider(): array
35 {
36 return [
37 'simple page - old style' => [
38 // original input value
39 '13',
40 // splitted values
41 [
42 'type' => LinkService::TYPE_PAGE,
43 'pageuid' => 13
44 ],
45 // final unified URN
46 't3://page?uid=13'
47 ],
48 'page with type - old style' => [
49 '13,31',
50 [
51 'type' => LinkService::TYPE_PAGE,
52 'pageuid' => 13,
53 'pagetype' => 31
54 ],
55 't3://page?uid=13&type=31'
56 ],
57 'page with type and fragment - old style' => [
58 '13,31#uncool',
59 [
60 'type' => LinkService::TYPE_PAGE,
61 'pageuid' => '13',
62 'pagetype' => '31',
63 'fragment' => 'uncool'
64 ],
65 't3://page?uid=13&type=31#uncool'
66 ],
67 'page with type and parameters and fragment - old style' => [
68 '13,31?unbel=ievable#uncool',
69 [
70 'type' => LinkService::TYPE_PAGE,
71 'pageuid' => '13',
72 'pagetype' => '31',
73 'parameters' => 'unbel=ievable',
74 'fragment' => 'uncool'
75 ],
76 't3://page?uid=13&type=31&unbel=ievable#uncool'
77 ],
78 'page with alias - old style' => [
79 'alias13',
80 [
81 'type' => LinkService::TYPE_PAGE,
82 'pagealias' => 'alias13'
83 ],
84 't3://page?alias=alias13'
85 ]
86 ];
87 }
88
89 /**
90 * @test
91 *
92 * @param string $input
93 * @param array $expected
94 * @param string $finalString
95 *
96 * @dataProvider resolveParametersForNonFilesDataProvider
97 */
98 public function resolveReturnsSplitParameters($input, $expected, $finalString): void
99 {
100 $subject = new LegacyLinkNotationConverter();
101 $this->assertEquals($expected, $subject->resolve($input));
102 }
103
104 /**
105 * @test
106 *
107 * @param string $input
108 * @param array $parameters
109 * @param string $expected
110 *
111 * @dataProvider resolveParametersForNonFilesDataProvider
112 */
113 public function splitParametersToUnifiedIdentifier($input, $parameters, $expected): void
114 {
115 $subject = new LinkService();
116 $this->assertEquals($expected, $subject->asString($parameters));
117 }
118
119 /**
120 * testing files and folders
121 */
122
123 /**
124 * Data provider for pointing to files
125 * t3:file:15
126 * t3:file:fileadmin/deep/down.jpg
127 * t3:file:1:myfolder/myidentifier.jpg
128 * t3:folder:1:myfolder
129 *
130 * @return array
131 */
132 public function resolveParametersForFilesDataProvider(): array
133 {
134 return [
135 'file without FAL - VERY old style' => [
136 'fileadmin/on/steroids.png',
137 [
138 'type' => LinkService::TYPE_FILE,
139 'file' => 'fileadmin/on/steroids.png'
140 ],
141 't3://file?identifier=fileadmin%2Fon%2Fsteroids.png'
142 ],
143 'file without FAL with file prefix - VERY old style' => [
144 'file:fileadmin/on/steroids.png',
145 [
146 'type' => LinkService::TYPE_FILE,
147 'file' => 'fileadmin/on/steroids.png'
148 ],
149 't3://file?identifier=fileadmin%2Fon%2Fsteroids.png'
150 ],
151 'file with FAL uid - old style' => [
152 'file:23',
153 [
154 'type' => LinkService::TYPE_FILE,
155 'file' => 23
156 ],
157 't3://file?uid=23'
158 ],
159 'folder without FAL - VERY old style' => [
160 'fileadmin/myimages/',
161 [
162 'type' => LinkService::TYPE_FOLDER,
163 'folder' => 'fileadmin/myimages/'
164 ],
165 't3://folder?storage=0&identifier=%2Ffileadmin%2Fmyimages%2F'
166 ],
167 'folder with combined identifier and file prefix (FAL) - old style' => [
168 'file:2:/myimages/',
169 [
170 'type' => LinkService::TYPE_FOLDER,
171 'folder' => '2:/myimages/'
172 ],
173 't3://folder?storage=2&identifier=%2Fmyimages%2F'
174 ],
175 ];
176 }
177
178 /**
179 * Helpful to know in which if() clause the stuff gets in
180 *
181 * @test
182 *
183 * @param string $input
184 * @param array $expected
185 * @param string $finalString
186 *
187 * @dataProvider resolveParametersForFilesDataProvider
188 */
189 public function resolveFileReferencesToSplitParameters($input, $expected, $finalString): void
190 {
191 $storage = $this->getMockBuilder(ResourceStorage::class)
192 ->disableOriginalConstructor()
193 ->getMock();
194
195 $factory = $this->getMockBuilder(ResourceFactory::class)
196 ->disableOriginalConstructor()
197 ->getMock();
198
199 // fake methods to return proper objects
200 if ($expected['type'] === LinkService::TYPE_FILE) {
201 $fileObject = new File(['identifier' => $expected['file']], $storage);
202 $factory->expects($this->any())->method('getFileObjectFromCombinedIdentifier')->with($expected['file'])
203 ->willReturn($fileObject);
204 $factory->expects($this->any())->method('retrieveFileOrFolderObject')->with($expected['file'])
205 ->willReturn($fileObject);
206 $factory->expects($this->any())->method('getFileObject')->with($expected['file'])->willReturn($fileObject);
207 $expected['file'] = $fileObject;
208 }
209 // fake methods to return proper objects
210 if ($expected['type'] === LinkService::TYPE_FOLDER) {
211 if (substr($expected['folder'], 0, 5) === 'file:') {
212 $expected['folder'] = substr($expected['folder'], 5);
213 }
214 $folderObject = new Folder($storage, $expected['folder'], $expected['folder']);
215 $factory->expects($this->any())->method('retrieveFileOrFolderObject')->with($expected['folder'])
216 ->willReturn($folderObject);
217 $factory->expects($this->any())->method('getFolderObjectFromCombinedIdentifier')->with($expected['folder'])
218 ->willReturn($folderObject);
219 $expected['folder'] = $folderObject;
220 }
221
222 /** @var LegacyLinkNotationConverter|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $subject */
223 $subject = $this->getAccessibleMock(LegacyLinkNotationConverter::class, ['dummy']);
224 $subject->_set('resourceFactory', $factory);
225 $this->assertEquals($expected, $subject->resolve($input));
226 }
227
228 /**
229 * Helpful to know in which if() clause the stuff gets in
230 *
231 * @test
232 *
233 * @param string $input
234 * @param array $parameters
235 * @param string $expected
236 *
237 * @dataProvider resolveParametersForFilesDataProvider
238 */
239 public function splitParametersToUnifiedIdentifierForFiles($input, $parameters, $expected): void
240 {
241 // fake methods to return proper objects
242 if ($parameters['type'] === LinkService::TYPE_FILE) {
243 $fileObject = $this->getMockBuilder(File::class)
244 ->setMethods(['getUid', 'getIdentifier'])
245 ->disableOriginalConstructor()
246 ->getMock();
247 $uid = 0;
248 if (MathUtility::canBeInterpretedAsInteger($parameters['file'])) {
249 $uid = $parameters['file'];
250 }
251 $fileObject->expects($this->once())->method('getUid')->willReturn($uid);
252 $fileObject->expects($this->any())->method('getIdentifier')->willReturn($parameters['file']);
253 $parameters['file'] = $fileObject;
254 }
255 // fake methods to return proper objects
256 if ($parameters['type'] === LinkService::TYPE_FOLDER) {
257 if (substr($parameters['folder'], 0, 5) === 'file:') {
258 $parameters['folder'] = substr($parameters['folder'], 5);
259 }
260 // fake "0" storage
261 if (!MathUtility::canBeInterpretedAsInteger($parameters['folder']{0})) {
262 $parameters['folder'] = '0:' . $parameters['folder'];
263 }
264 $folderObject = $this->getMockBuilder(Folder::class)
265 ->setMethods(['getCombinedIdentifier', 'getStorage', 'getIdentifier'])
266 ->disableOriginalConstructor()
267 ->getMock();
268 $folderObject->expects($this->any())->method('getCombinedIdentifier')->willReturn($parameters['folder']);
269 $folderData = explode(':', $parameters['folder']);
270 /** @var ResourceStorage|\PHPUnit_Framework_MockObject_MockObject $storageMock */
271 $storage = $this->getMockBuilder(ResourceStorage::class)
272 ->disableOriginalConstructor()
273 ->getMock(['getUid']);
274 $storage->method('getUid')->willReturn($folderData[0]);
275 $folderObject->expects($this->any())->method('getStorage')->willReturn($storage);
276 $folderObject->expects($this->any())->method('getIdentifier')->willReturn($folderData[1]);
277 $parameters['folder'] = $folderObject;
278 }
279
280 $subject = new LinkService();
281 $this->assertEquals($expected, $subject->asString($parameters));
282 }
283
284 /**
285 * @return array
286 */
287 public function resolveThrowExceptionWithPharReferencesDataProvider(): array
288 {
289 return [
290 'URL encoded local' => [
291 'phar%3a//some-file.jpg',
292 ],
293 'URL encoded absolute' => [
294 'phar%3a///path/some-file.jpg',
295 ],
296 'not URL encoded local' => [
297 'phar://some-file.jpg',
298 ],
299 'not URL encoded absolute' => [
300 'phar:///path/some-file.jpg',
301 ],
302 ];
303 }
304
305 /**
306 * @test
307 * @dataProvider resolveThrowExceptionWithPharReferencesDataProvider
308 */
309 public function resolveThrowExceptionWithPharReferences(string $pharUrl)
310 {
311 $this->expectException(\RuntimeException::class);
312 $this->expectExceptionCode(1530030673);
313 (new LegacyLinkNotationConverter())->resolve($pharUrl);
314 }
315 }