19c2ad42ff9c22053c3c62eb068cc749c0ffaf20
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Tests / Unit / Http / RequestHandlerTest.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Frontend\Tests\Unit\Http;
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 Prophecy\Argument;
19 use TYPO3\CMS\Core\Http\ServerRequestFactory;
20 use TYPO3\CMS\Core\Page\PageRenderer;
21 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
22 use TYPO3\CMS\Core\TypoScript\TemplateService;
23 use TYPO3\CMS\Core\Utility\GeneralUtility;
24 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
25 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
26 use TYPO3\CMS\Frontend\Http\RequestHandler;
27 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
28
29 /**
30 * Test case
31 */
32 class RequestHandlerTest extends UnitTestCase
33 {
34 /**
35 * @return array
36 */
37 public function generateMetaTagHtmlGeneratesCorrectTagsDataProvider()
38 {
39 return [
40 'simple meta' => [
41 [
42 'author' => 'Markus Klein',
43 ],
44 '',
45 [
46 'type' => 'name',
47 'name' => 'author',
48 'content' => 'Markus Klein'
49 ]
50 ],
51 'httpEquivalent meta' => [
52 [
53 'X-UA-Compatible' => 'IE=edge,chrome=1',
54 'X-UA-Compatible.' => ['httpEquivalent' => 1]
55 ],
56 'IE=edge,chrome=1',
57 [
58 'type' => 'http-equiv',
59 'name' => 'X-UA-Compatible',
60 'content' => 'IE=edge,chrome=1'
61 ]
62 ],
63 'httpEquivalent meta xhtml new notation' => [
64 [
65 'X-UA-Compatible' => 'IE=edge,chrome=1',
66 'X-UA-Compatible.' => ['attribute' => 'http-equiv']
67 ],
68 'IE=edge,chrome=1',
69 [
70 'type' => 'http-equiv',
71 'name' => 'X-UA-Compatible',
72 'content' => 'IE=edge,chrome=1'
73 ]
74 ],
75 'refresh meta' => [
76 [
77 'refresh' => '10',
78 ],
79 '',
80 [
81 'type' => 'http-equiv',
82 'name' => 'refresh',
83 'content' => '10'
84 ]
85 ],
86 'refresh meta new notation' => [
87 [
88 'refresh' => '10',
89 'refresh.' => ['attribute' => 'http-equiv']
90 ],
91 '10',
92 [
93 'type' => 'http-equiv',
94 'name' => 'refresh',
95 'content' => '10'
96 ]
97 ],
98 'meta with dot' => [
99 [
100 'DC.author' => 'Markus Klein',
101 ],
102 '',
103 [
104 'type' => 'name',
105 'name' => 'DC.author',
106 'content' => 'Markus Klein'
107 ]
108 ],
109 'meta with colon' => [
110 [
111 'OG:title' => 'Magic Tests',
112 ],
113 '',
114 [
115 'type' => 'name',
116 'name' => 'OG:title',
117 'content' => 'Magic Tests'
118 ]
119 ],
120 'different attribute name' => [
121 [
122 'og:site_title' => 'My TYPO3 site',
123 'og:site_title.' => ['attribute' => 'property'],
124 ],
125 'My TYPO3 site',
126 [
127 'type' => 'property',
128 'name' => 'og:site_title',
129 'content' => 'My TYPO3 site'
130 ]
131 ],
132 'meta with 0 value' => [
133 [
134 'custom:key' => '0',
135 ],
136 '',
137 [
138 'type' => 'name',
139 'name' => 'custom:key',
140 'content' => '0'
141 ]
142 ],
143 ];
144 }
145
146 /**
147 * @test
148 */
149 public function generateMetaTagExpectExceptionOnBogusTags()
150 {
151 $stdWrapResult = '10';
152
153 $typoScript = [
154 'refresh' => '10',
155 'refresh.' => ['attribute' => 'http-equiv-new']
156 ];
157
158 $expectedTags = [
159 'type' => 'http-equiv-new',
160 'name' => 'refresh',
161 'content' => '10'
162 ];
163
164 $cObj = $this->prophesize(ContentObjectRenderer::class);
165 $cObj->cObjGet(Argument::cetera())->shouldBeCalled();
166 $cObj->stdWrap(Argument::cetera())->willReturn($stdWrapResult);
167 $tmpl = $this->prophesize(TemplateService::class);
168 $tsfe = $this->prophesize(TypoScriptFrontendController::class);
169 $tsfe->generatePageTitle()->willReturn('');
170 $tsfe->INTincScript_loadJSCode()->shouldBeCalled();
171 $tsfe->cObj = $cObj->reveal();
172 $tsfe->tmpl = $tmpl->reveal();
173 $tsfe->page = [
174 'title' => ''
175 ];
176 $tsfe->pSetup = [
177 'meta.' => $typoScript
178 ];
179
180 $pageRendererProphecy = $this->prophesize(PageRenderer::class);
181 $subject = $this->getAccessibleMock(RequestHandler::class, ['getPageRenderer'], [], '', false);
182 $subject->expects($this->any())->method('getPageRenderer')->willReturn($pageRendererProphecy->reveal());
183 $subject->_set('timeTracker', new TimeTracker(false));
184 $subject->_call('generatePageContentWithHeader', $tsfe->reveal(), null);
185
186 $pageRendererProphecy->setMetaTag($expectedTags['type'], $expectedTags['name'], $expectedTags['content'])->willThrow(\InvalidArgumentException::class);
187 }
188
189 /**
190 * @test
191 * @dataProvider generateMetaTagHtmlGeneratesCorrectTagsDataProvider
192 *
193 * @param array $typoScript
194 * @param string $stdWrapResult
195 * @param array $expectedTags
196 */
197 public function generateMetaTagHtmlGeneratesCorrectTags(array $typoScript, string $stdWrapResult, array $expectedTags)
198 {
199 $cObj = $this->prophesize(ContentObjectRenderer::class);
200 $cObj->cObjGet(Argument::cetera())->shouldBeCalled();
201 $cObj->stdWrap(Argument::cetera())->willReturn($stdWrapResult);
202 $tmpl = $this->prophesize(TemplateService::class);
203 $tsfe = $this->prophesize(TypoScriptFrontendController::class);
204 $tsfe->generatePageTitle()->willReturn('');
205 $tsfe->INTincScript_loadJSCode()->shouldBeCalled();
206 $tsfe->cObj = $cObj->reveal();
207 $tsfe->tmpl = $tmpl->reveal();
208 $tsfe->config = [
209 'config' => [],
210 ];
211 $tsfe->page = [
212 'title' => ''
213 ];
214 $tsfe->pSetup = [
215 'meta.' => $typoScript
216 ];
217 $pageRendererProphecy = $this->prophesize(PageRenderer::class);
218 $subject = $this->getAccessibleMock(RequestHandler::class, ['getPageRenderer'], [], '', false);
219 $subject->expects($this->any())->method('getPageRenderer')->willReturn($pageRendererProphecy->reveal());
220 $subject->_set('timeTracker', new TimeTracker(false));
221 $subject->_call('generatePageContentWithHeader', $tsfe->reveal(), null);
222
223 $pageRendererProphecy->setMetaTag($expectedTags['type'], $expectedTags['name'], $expectedTags['content'], [], false)->shouldHaveBeenCalled();
224 }
225
226 /**
227 * @test
228 */
229 public function generateMetaTagHtmlGenerateNoTagWithEmptyContent()
230 {
231 $stdWrapResult = '';
232
233 $typoScript = [
234 'custom:key' => '',
235 ];
236
237 $cObj = $this->prophesize(ContentObjectRenderer::class);
238 $cObj->cObjGet(Argument::cetera())->shouldBeCalled();
239 $cObj->stdWrap(Argument::cetera())->willReturn($stdWrapResult);
240 $tmpl = $this->prophesize(TemplateService::class);
241 $tsfe = $this->prophesize(TypoScriptFrontendController::class);
242 $tsfe->generatePageTitle()->willReturn('');
243 $tsfe->INTincScript_loadJSCode()->shouldBeCalled();
244 $tsfe->cObj = $cObj->reveal();
245 $tsfe->tmpl = $tmpl->reveal();
246 $tsfe->config = [
247 'config' => [],
248 ];
249 $tsfe->page = [
250 'title' => ''
251 ];
252 $tsfe->pSetup = [
253 'meta.' => $typoScript
254 ];
255
256 $pageRendererProphecy = $this->prophesize(PageRenderer::class);
257 $subject = $this->getAccessibleMock(RequestHandler::class, ['getPageRenderer'], [], '', false);
258 $subject->expects($this->any())->method('getPageRenderer')->willReturn($pageRendererProphecy->reveal());
259 $subject->_set('timeTracker', new TimeTracker(false));
260 $subject->_call('generatePageContentWithHeader', $tsfe->reveal(), null);
261
262 $pageRendererProphecy->setMetaTag(null, null, null)->shouldNotBeCalled();
263 }
264
265 public function generateMultipleMetaTagsDataProvider()
266 {
267 return [
268 'multi value attribute name' => [
269 [
270 'og:locale:alternate.' => [
271 'attribute' => 'property',
272 'value' => [
273 10 => 'nl_NL',
274 20 => 'de_DE',
275 ]
276 ],
277 ],
278 '',
279 [
280 [
281 'type' => 'property',
282 'name' => 'og:locale:alternate',
283 'content' => 'nl_NL'
284 ],
285 [
286 'type' => 'property',
287 'name' => 'og:locale:alternate',
288 'content' => 'de_DE'
289 ]
290 ]
291 ],
292 'multi value attribute name (empty values are skipped)' => [
293 [
294 'og:locale:alternate.' => [
295 'attribute' => 'property',
296 'value' => [
297 10 => 'nl_NL',
298 20 => '',
299 30 => 'de_DE',
300 ]
301 ],
302 ],
303 '',
304 [
305 [
306 'type' => 'property',
307 'name' => 'og:locale:alternate',
308 'content' => 'nl_NL'
309 ],
310 [
311 'type' => 'property',
312 'name' => 'og:locale:alternate',
313 'content' => 'de_DE'
314 ]
315 ],
316 ],
317 ];
318 }
319
320 /**
321 * @test
322 * @dataProvider generateMultipleMetaTagsDataProvider
323 *
324 * @param array $typoScript
325 * @param string $stdWrapResult
326 * @param array $expectedTags
327 */
328 public function generateMultipleMetaTags(array $typoScript, string $stdWrapResult, array $expectedTags)
329 {
330 $cObj = $this->prophesize(ContentObjectRenderer::class);
331 $cObj->cObjGet(Argument::cetera())->shouldBeCalled();
332 $cObj->stdWrap(Argument::cetera())->willReturn($stdWrapResult);
333 $tmpl = $this->prophesize(TemplateService::class);
334 $tsfe = $this->prophesize(TypoScriptFrontendController::class);
335 $tsfe->generatePageTitle()->willReturn('');
336 $tsfe->INTincScript_loadJSCode()->shouldBeCalled();
337 $tsfe->cObj = $cObj->reveal();
338 $tsfe->tmpl = $tmpl->reveal();
339 $tsfe->config = [
340 'config' => [],
341 ];
342 $tsfe->page = [
343 'title' => ''
344 ];
345 $tsfe->pSetup = [
346 'meta.' => $typoScript
347 ];
348 $pageRendererProphecy = $this->prophesize(PageRenderer::class);
349 $subject = $this->getAccessibleMock(RequestHandler::class, ['getPageRenderer'], [], '', false);
350 $subject->expects($this->any())->method('getPageRenderer')->willReturn($pageRendererProphecy->reveal());
351 $subject->_set('timeTracker', new TimeTracker(false));
352 $subject->_call('generatePageContentWithHeader', $tsfe->reveal(), null);
353
354 $pageRendererProphecy->setMetaTag($expectedTags[0]['type'], $expectedTags[0]['name'], $expectedTags[0]['content'], [], false)->shouldHaveBeenCalled();
355 $pageRendererProphecy->setMetaTag($expectedTags[1]['type'], $expectedTags[1]['name'], $expectedTags[1]['content'], [], false)->shouldHaveBeenCalled();
356 }
357
358 /**
359 * Test if the method is called, and the object is still the same.
360 *
361 * @test
362 */
363 public function addModifiedGlobalsToIncomingRequestFindsSameObject()
364 {
365 GeneralUtility::flushInternalRuntimeCaches();
366 $_SERVER['REQUEST_METHOD'] = 'POST';
367 $_SERVER['HTTP_HOST'] = 'https://www.example.com/my/path/';
368 $_GET = ['foo' => '1'];
369 $_POST = ['bar' => 'yo'];
370 $request = ServerRequestFactory::fromGlobals();
371 $request = $request->withAttribute('_originalGetParameters', $_GET);
372 $request = $request->withAttribute('_originalPostParameters', $_POST);
373
374 $subject = $this->getAccessibleMock(RequestHandler::class, ['dummy'], [], '', false);
375 $resultRequest = $subject->_call('addModifiedGlobalsToIncomingRequest', $request);
376 $this->assertSame($request, $resultRequest);
377 }
378
379 /**
380 * @return array
381 */
382 public function addModifiedGlobalsToIncomingRequestDataProvider()
383 {
384 return [
385 'No parameters have been modified via hook or middleware' => [
386 ['batman' => '1'],
387 ['no_cache' => 1],
388 // Enriched within PSR-7 query params + parsed body
389 [],
390 [],
391 // modified GET / POST parameters
392 [],
393 [],
394 // expected merged results
395 ['batman' => '1'],
396 ['no_cache' => 1],
397 ],
398 'No parameters have been modified via hook' => [
399 ['batman' => '1'],
400 [],
401 // Enriched within PSR-7 query params + parsed body
402 ['ARD' => 'TV', 'Oscars' => 'Cinema'],
403 ['no_cache' => '1'],
404 // modified GET / POST parameters
405 [],
406 [],
407 // expected merged results
408 ['batman' => '1', 'ARD' => 'TV', 'Oscars' => 'Cinema'],
409 ['no_cache' => 1],
410 ],
411 'Hooks and Middlewares modified' => [
412 ['batman' => '1'],
413 [],
414 // Enriched within PSR-7 query params + parsed body
415 ['ARD' => 'TV', 'Oscars' => 'Cinema'],
416 ['no_cache' => '1'],
417 // modified GET / POST parameters
418 ['batman' => '1', 'add_via_hook' => 'yes'],
419 ['submitForm' => 'download now'],
420 // expected merged results
421 ['batman' => '1', 'add_via_hook' => 'yes', 'ARD' => 'TV', 'Oscars' => 'Cinema'],
422 ['submitForm' => 'download now', 'no_cache' => 1],
423 ],
424 'Hooks and Middlewares modified with middleware overruling hooks' => [
425 ['batman' => '1'],
426 [],
427 // Enriched within PSR-7 query params + parsed body
428 ['ARD' => 'TV', 'Oscars' => 'Cinema'],
429 ['no_cache' => '1'],
430 // modified GET / POST parameters
431 ['batman' => '0', 'add_via_hook' => 'yes'],
432 ['submitForm' => 'download now', 'no_cache' => 0],
433 // expected merged results
434 ['batman' => '1', 'add_via_hook' => 'yes', 'ARD' => 'TV', 'Oscars' => 'Cinema'],
435 ['submitForm' => 'download now', 'no_cache' => 1],
436 ],
437 'Hooks and Middlewares modified with middleware overruling hooks with nested parameters' => [
438 ['batman' => '1'],
439 [['tx_siteexample_pi2' => ['uid' => 13]]],
440 // Enriched within PSR-7 query params + parsed body
441 ['ARD' => 'TV', 'Oscars' => 'Cinema', ['tx_blogexample_pi1' => ['uid' => 123]]],
442 ['no_cache' => '1', ['tx_siteexample_pi2' => ['name' => 'empty-tail']]],
443 // modified GET / POST parameters
444 ['batman' => '0', 'add_via_hook' => 'yes', ['tx_blogexample_pi1' => ['uid' => 234]]],
445 ['submitForm' => 'download now', 'no_cache' => 0],
446 // expected merged results
447 ['batman' => '1', 'add_via_hook' => 'yes', 'ARD' => 'TV', 'Oscars' => 'Cinema', ['tx_blogexample_pi1' => ['uid' => 123]]],
448 ['submitForm' => 'download now', 'no_cache' => '1', ['tx_siteexample_pi2' => ['uid' => 13, 'name' => 'empty-tail']]],
449 ],
450 ];
451 }
452
453 /**
454 * Test if the method modifies GET and POST to the expected result, when enriching an object.
455 *
456 * @param array $initialGetParams
457 * @param array $initialPostParams
458 * @param array $addedQueryParams
459 * @param array $addedParsedBody
460 * @param array $modifiedGetParams
461 * @param array $modifiedPostParams
462 * @param array $expectedQueryParams
463 * @param array $expectedParsedBody
464 * @dataProvider addModifiedGlobalsToIncomingRequestDataProvider
465 * @test
466 */
467 public function addModifiedGlobalsToIncomingRequestModifiesObject(
468 $initialGetParams,
469 $initialPostParams,
470 $addedQueryParams,
471 $addedParsedBody,
472 $modifiedGetParams,
473 $modifiedPostParams,
474 $expectedQueryParams,
475 $expectedParsedBody
476 ) {
477 GeneralUtility::flushInternalRuntimeCaches();
478 $_SERVER['REQUEST_METHOD'] = 'POST';
479 $_SERVER['HTTP_HOST'] = 'https://www.example.com/my/path/';
480 $_GET = $initialGetParams;
481 $_POST = $initialPostParams;
482 $request = ServerRequestFactory::fromGlobals();
483 $request = $request->withAttribute('_originalGetParameters', $initialGetParams);
484 $request = $request->withAttribute('_originalPostParameters', $initialPostParams);
485
486 // Now enriching the request object with other GET / POST parameters
487 $queryParams = $request->getQueryParams();
488 $queryParams = array_replace_recursive($queryParams, $addedQueryParams);
489 $request = $request->withQueryParams($queryParams);
490 $parsedBody = $request->getParsedBody() ?? [];
491 $parsedBody = array_replace_recursive($parsedBody, $addedParsedBody);
492 $request = $request->withParsedBody($parsedBody);
493
494 // Now overriding GET and POST parameters
495 $_GET = $modifiedGetParams;
496 $_POST = $modifiedPostParams;
497
498 $subject = $this->getAccessibleMock(RequestHandler::class, ['dummy'], [], '', false);
499 $subject->_set('timeTracker', new TimeTracker(false));
500 $resultRequest = $subject->_call('addModifiedGlobalsToIncomingRequest', $request);
501 $this->assertEquals($expectedQueryParams, $resultRequest->getQueryParams());
502 $this->assertEquals($expectedParsedBody, $resultRequest->getParsedBody());
503 }
504
505 /**
506 * Test if the method is called, and the globals are still the same after calling the method
507 *
508 * @test
509 */
510 public function resetGlobalsToCurrentRequestDoesNotModifyAnything()
511 {
512 $getVars = ['outside' => '1'];
513 $postVars = ['world' => 'yo'];
514 GeneralUtility::flushInternalRuntimeCaches();
515 $_SERVER['REQUEST_METHOD'] = 'POST';
516 $_SERVER['HTTP_HOST'] = 'https://www.example.com/my/path/';
517 $_GET = $getVars;
518 $_POST = $postVars;
519 $request = ServerRequestFactory::fromGlobals();
520
521 $subject = $this->getAccessibleMock(RequestHandler::class, ['dummy'], [], '', false);
522 $subject->_call('resetGlobalsToCurrentRequest', $request);
523 $this->assertEquals($_GET, $getVars);
524 $this->assertEquals($_POST, $postVars);
525 }
526
527 /**
528 * Test if the method is called, and the globals are still the same after calling the method
529 *
530 * @test
531 */
532 public function resetGlobalsToCurrentRequestWithModifiedRequestOverridesGlobals()
533 {
534 $getVars = ['typical' => '1'];
535 $postVars = ['mixtape' => 'wheels'];
536 $modifiedGetVars = ['typical' => 1, 'dont-stop' => 'the-music'];
537 $modifiedPostVars = ['mixtape' => 'wheels', 'tx_blogexample_pi1' => ['uid' => 13]];
538 GeneralUtility::flushInternalRuntimeCaches();
539 $_SERVER['REQUEST_METHOD'] = 'POST';
540 $_SERVER['HTTP_HOST'] = 'https://www.example.com/my/path/';
541 $_GET = $getVars;
542 $_POST = $postVars;
543 $request = ServerRequestFactory::fromGlobals();
544 $request = $request->withQueryParams($modifiedGetVars);
545 $request = $request->withParsedBody($modifiedPostVars);
546
547 $subject = $this->getAccessibleMock(RequestHandler::class, ['dummy'], [], '', false);
548 $subject->_call('resetGlobalsToCurrentRequest', $request);
549 $this->assertEquals($_GET, $modifiedGetVars);
550 $this->assertEquals($_POST, $modifiedPostVars);
551 }
552 }