‪TYPO3CMS  ‪main
RedirectServiceTest.php
Go to the documentation of this file.
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 
19 
20 use Psr\EventDispatcher\EventDispatcherInterface;
21 use Psr\Log\NullLogger;
22 use Symfony\Component\DependencyInjection\Container;
37 use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
38 use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
39 
40 final class ‪RedirectServiceTest extends FunctionalTestCase
41 {
43 
44  protected const ‪LANGUAGE_PRESETS = [
45  'EN' => ['id' => 0, 'title' => 'English', 'locale' => 'en_US.UTF8'],
46  ];
47 
48  protected array ‪$coreExtensionsToLoad = ['redirects'];
49 
50  protected array ‪$configurationToUseInTestInstance = [
51  'FE' => [
52  'cacheHash' => [
53  'excludedParameters' => ['L', 'pk_campaign', 'pk_kwd', 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'gclid', 'fbclid', 'msclkid'],
54  // @todo this should be tested explicitly - enabled and disabled
55  'enforceValidation' => false,
56  ],
57  ],
58  ];
59 
60  protected function ‪setUp(): void
61  {
62  parent::setUp();
63  $this->importCSVDataSet(__DIR__ . '/Fixtures/be_users.csv');
64  $this->setUpBackendUser(1);
65  }
66 
71  {
72  $this->importCSVDataSet(__DIR__ . '/Fixtures/RedirectToAccessRestrictedPages.csv');
73 
75  'acme-com',
76  $this->‪buildSiteConfiguration(1, 'https://acme.com/')
77  );
78 
79  $this->setUpFrontendRootPage(1, ['EXT:redirects/Tests/Functional/Service/Fixtures/Redirects.typoscript']);
80 
81  $logger = new NullLogger();
82  $frontendUserAuthentication = new ‪FrontendUserAuthentication();
83  $frontendUserAuthentication->setLogger($logger);
84 
85  $siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
86  $uri = new ‪Uri('https://acme.com/redirect-to-access-restricted-site');
87  $request = ‪$GLOBALS['TYPO3_REQUEST'] = (new ‪ServerRequest($uri))
88  ->withAttribute('site', $siteFinder->getSiteByRootPageId(1))
89  ->withAttribute('frontend.user', $frontendUserAuthentication)
90  ->withAttribute('applicationType', ‪SystemEnvironmentBuilder::REQUESTTYPE_FE);
91 
92  $linkServiceMock = $this->getMockBuilder(LinkService::class)->disableOriginalConstructor()->getMock();
93  $linkServiceMock->method('resolve')->with('t3://page?uid=2')->willReturn(
94  [
95  'pageuid' => 2,
96  'type' => ‪LinkService::TYPE_PAGE,
97  ]
98  );
99 
100  $redirectService = new ‪RedirectService(
102  $linkServiceMock,
103  $siteFinder,
105  );
106  $redirectService->setLogger($logger);
107 
108  // Assert correct redirect is matched
109  $redirectMatch = $redirectService->matchRedirect($uri->getHost(), $uri->getPath(), $uri->getQuery());
110  self::assertEquals(1, $redirectMatch['uid']);
111  self::assertEquals('t3://page?uid=2', $redirectMatch['target']);
112 
113  // Ensure we deal with an unauthorized request!
114  self::assertFalse(GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('frontend.user', 'isLoggedIn'));
115 
116  // Assert link to access restricted page is build
117  ‪$targetUrl = $redirectService->getTargetUrl($redirectMatch, $request);
118  self::assertEquals(new ‪Uri('https://acme.com/access-restricted'), ‪$targetUrl);
119  }
120 
121  public static function ‪redirectsDataProvider(): array
122  {
123  return [
124  [
125  'https://acme.com/redirect-301',
126  301,
127  'https://acme.com/',
128  1,
129  ],
130  [
131  'https://acme.com/redirect-308',
132  308,
133  'https://acme.com/page2',
134  2,
135  ],
136  [
137  'https://acme.com/redirect-302',
138  302,
139  'https://www.typo3.org',
140  3,
141  ],
142  ];
143  }
144 
149  public function ‪checkReponseCodeOnRedirect(‪$url, ‪$statusCode, ‪$targetUrl, $redirectUid): void
150  {
151  $this->importCSVDataSet(__DIR__ . '/Fixtures/RedirectToPages.csv');
152 
154  'acme-com',
155  $this->‪buildSiteConfiguration(1, 'https://acme.com/')
156  );
157 
158  $this->setUpFrontendRootPage(
159  1,
160  ['EXT:redirects/Tests/Functional/Service/Fixtures/Redirects.typoscript']
161  );
162 
163  $response = $this->executeFrontendSubRequest(
164  new InternalRequest(‪$url)
165  );
166  self::assertEquals(‪$statusCode, $response->getStatusCode());
167  self::assertIsArray($response->getHeader('X-Redirect-By'));
168  self::assertIsArray($response->getHeader('location'));
169  self::assertEquals('TYPO3 Redirect ' . $redirectUid, $response->getHeader('X-Redirect-By')[0]);
170  self::assertEquals(‪$targetUrl, $response->getHeader('location')[0]);
171  }
172 
173  public static function ‪checkRegExpRedirectsDataProvider(): array
174  {
175  return [
176  'regexp redirect respecting query parameter but not keeping them' => [
177  'https://acme.com/index.php?option=com_content&page=some_page',
178  301,
179  'https://anotherdomain.com/some_page',
180  1,
181  ],
182  'regexp redirect respecting query parameter and keeping them' => [
183  'https://acme.com/index.php?option=com_content2&page=some_page',
184  301,
185  'https://anotherdomain.com/some_page?option=com_content2&page=some_page',
186  2,
187  ],
188  'regexp redirect not respecting query parameters and not keeping them' => [
189  'https://acme.com/some-old-page-others?option=com_content',
190  301,
191  'https://anotherdomain.com/others',
192  3,
193  ],
194  'regexp redirect not respecting query parameters but keeping them' => [
195  'https://acme.com/some-page-others',
196  301,
197  'https://anotherdomain.com/others',
198  4,
199  ],
200  'regexp redirect not respecting query parameters and not keeping them, with query parameter in request' => [
201  'https://acme.com/some-old-page-others?option=com_content',
202  301,
203  'https://anotherdomain.com/others',
204  3,
205  ],
206  'regexp redirect not respecting query parameters but keeping them, without query parameter in request' => [
207  'https://acme.com/some-page-others',
208  301,
209  'https://anotherdomain.com/others',
210  4,
211  ],
212  // check against unsafe regexp captching group
213  'regexp redirect with unsafe captching group, respecting query parameters and not keeping them, with query parameter in request' => [
214  'https://acme.com/unsafe-captchinggroup-matching-queryparameters-others?option=com_content',
215  301,
216  'https://anotherdomain.com/others',
217  5,
218  ],
219  // checks against unsafe regexp captching group, but as keeping query parameters this may be undetected,
220  // and as such this test acts as counterpart to tests above
221  'regexp redirect with unsafe captching group, respecting query parameters but keeping them, with query parameter in request' => [
222  'https://acme.com/another-unsafe-captchinggroup-matching-queryparameters-others?option=com_content',
223  301,
224  'https://anotherdomain.com/others?option=com_content',
225  6,
226  ],
227  // check against safe regexp captching group
228  'regexp redirect safe captching group, respecting query parameters and not keeping them, with query parameter in request' => [
229  'https://acme.com/safe-captchinggroup-not-matching-queryparameters-others?option=com_content',
230  301,
231  'https://anotherdomain.com/others',
232  7,
233  ],
234  // checks against safe regexp captching group
235  'regexp redirect safe captching group, respecting query parameters but keeping them, with query parameter in request' => [
236  'https://acme.com/another-safe-captchinggroup-not-matching-queryparameters-others?option=com_content',
237  301,
238  'https://anotherdomain.com/others?option=com_content',
239  8,
240  ],
241  // check against more safe regexp captching group - this tests path fallback even with queryparameters in
242  // request for non query regexp with $ as end matching in regexp
243  'regexp redirect safe captching group, not respecting query parameters and not keeping them, with query parameter in request' => [
244  'https://acme.com/more-safe-captchinggroup-not-matching-queryparameters-others?option=com_content',
245  301,
246  'https://anotherdomain.com/others',
247  9,
248  ],
249  'regexp redirect safe captching group, not respecting query parameters but keeping them, with query parameter in request' => [
250  'https://acme.com/another-more-safe-captchinggroup-not-matching-queryparameters-others?option=com_content',
251  301,
252  'https://anotherdomain.com/others?option=com_content',
253  10,
254  ],
255  'regexp capture group with relative target' => [
256  'https://acme.com/relative-target-page2',
257  301,
258  '/page2',
259  11,
260  ],
261  'regexp capture group with relative target - keep query params' => [
262  'https://acme.com/relative-target-keep-page2?param1=value1',
263  301,
264  '/page2?param1=value1',
265  12,
266  ],
267  'regexp capture group with relative target - respect query param' => [
268  'https://acme.com/respect-relative-target-page2?param1=subpage',
269  301,
270  '/page2/subpage',
271  13,
272  ],
273  'regexp capture group with relative target - respect query param and keep them' => [
274  'https://acme.com/respect-keep-relative-target-page2?param1=subpage',
275  301,
276  '/page2/subpage?param1=subpage',
277  14,
278  ],
279  // test for https://forge.typo3.org/issues/89799#note-14
280  'regexp relative target redirect with unsafe regexp and without ending $' => [
281  'https://acme.com/other-relative-target-with-unsafe-capture-group-new',
282  301,
283  '/offer-new',
284  15,
285  ],
286  // test for https://forge.typo3.org/issues/89799#note-14
287  'regexp redirect with unsafe regexp and without ending $' => [
288  'https://acme.com/other-redirect-with-unsafe-capture-group-new',
289  301,
290  'https://anotherdomain.com/offernew',
291  16,
292  ],
293  ];
294  }
295 
300  public function ‪checkRegExpRedirects(string ‪$url, int $expectedStatusCode, string $expectedRedirectUri, int $expectedRedirectUid)
301  {
302  $this->importCSVDataSet(__DIR__ . '/Fixtures/RedirectService_regexp.csv');
304  'acme-com',
305  $this->‪buildSiteConfiguration(1, 'https://acme.com/')
306  );
307  $this->setUpFrontendRootPage(
308  1,
309  ['EXT:redirects/Tests/Functional/Service/Fixtures/Redirects.typoscript']
310  );
311 
312  $response = $this->executeFrontendSubRequest(
313  new InternalRequest(‪$url),
314  null,
315  false
316  );
317  self::assertEquals($expectedStatusCode, $response->getStatusCode());
318  self::assertIsArray($response->getHeader('X-Redirect-By'));
319  self::assertIsArray($response->getHeader('location'));
320  self::assertEquals('TYPO3 Redirect ' . $expectedRedirectUid, $response->getHeader('X-Redirect-By')[0]);
321  self::assertEquals($expectedRedirectUri, $response->getHeader('location')[0]);
322  }
323 
329  {
330  ‪$url = 'https://acme.com/regexp-respect-get-parameter?param1=value1';
331  $expectedStatusCode = 404;
332  $this->importCSVDataSet(__DIR__ . '/Fixtures/RedirectService_regexp.csv');
334  'acme-com',
335  $this->‪buildSiteConfiguration(1, 'https://acme.com/')
336  );
337  $this->setUpFrontendRootPage(
338  1,
339  ['EXT:redirects/Tests/Functional/Service/Fixtures/Redirects.typoscript']
340  );
341 
342  $response = $this->executeFrontendSubRequest(
343  new InternalRequest(‪$url),
344  null,
345  false
346  );
347  self::assertSame($expectedStatusCode, $response->getStatusCode());
348  }
349 
355  {
356  ‪$url = 'https://acme.com/regexp-respect-get-parameter';
357  $expectedStatusCode = 301;
358  $expectedRedirectUid = 17;
359  $expectedRedirectUri = 'https://anotherdomain.com/regexp-respect-get-parameter';
360  $this->importCSVDataSet(__DIR__ . '/Fixtures/RedirectService_regexp.csv');
362  'acme-com',
363  $this->‪buildSiteConfiguration(1, 'https://acme.com/')
364  );
365  $this->setUpFrontendRootPage(
366  1,
367  ['EXT:redirects/Tests/Functional/Service/Fixtures/Redirects.typoscript']
368  );
369 
370  $response = $this->executeFrontendSubRequest(
371  new InternalRequest(‪$url),
372  null,
373  false
374  );
375  self::assertSame($expectedStatusCode, $response->getStatusCode());
376  self::assertIsArray($response->getHeader('X-Redirect-By'));
377  self::assertIsArray($response->getHeader('location'));
378  self::assertSame('TYPO3 Redirect ' . $expectedRedirectUid, $response->getHeader('X-Redirect-By')[0]);
379  self::assertSame($expectedRedirectUri, $response->getHeader('location')[0]);
380  }
381 
382  public static function ‪samePathWithSameDomainT3TargetDataProvider(): array
383  {
384  return [
385  'flat' => [
386  'https://acme.com/flat-samehost-1',
387  'https://acme.com/',
388  200,
389  null,
390  null,
391  ],
392  // this should redirect and not pass through
393  'flat - with query parameters' => [
394  'https://acme.com/flat-samehost-1?param1=value1&cHash=e0527192caa60a6dac1e30af7cfeaf64',
395  'https://acme.com/',
396  301,
397  'https://acme.com/flat-samehost-1',
398  1,
399  ],
400  'flat keep_query_parameters' => [
401  'https://acme.com/flat-samehost-2',
402  'https://acme.com/',
403  200,
404  null,
405  null,
406  ],
407  'flat keep_query_parameters - with query parameters' => [
408  'https://acme.com/flat-samehost-2?param1=value1&cHash=e0527192caa60a6dac1e30af7cfeaf64',
409  'https://acme.com/',
410  200,
411  null,
412  null,
413  ],
414  'flat respect_query_parameters' => [
415  'https://acme.com/flat-samehost-3',
416  'https://acme.com/',
417  200,
418  null,
419  null,
420  ],
421  // this should redirect and not pass through
422  'flat respect_query_parameters - with query parameters' => [
423  'https://acme.com/flat-samehost-3?param1=value1',
424  'https://acme.com/',
425  301,
426  'https://acme.com/flat-samehost-3',
427  3,
428  ],
429  'flat respect_query_parameters and keep_query_parameters' => [
430  'https://acme.com/flat-samehost-4',
431  'https://acme.com/',
432  200,
433  null,
434  null,
435  ],
436  'flat respect_query_parameters and keep_query_parameters - with query parameters' => [
437  'https://acme.com/flat-samehost-4?param1=value1&cHash=caa2156411affc2d7c8c5169652c6e13',
438  'https://acme.com/',
439  200,
440  null,
441  null,
442  ],
443  'regexp' => [
444  'https://acme.com/regexp-samehost-1',
445  'https://acme.com/',
446  200,
447  null,
448  null,
449  ],
450  // this should redirect and not pass through
451  'regexp - with query parameters' => [
452  'https://acme.com/regexp-samehost-1?param1=value1',
453  'https://acme.com/',
454  301,
455  'https://acme.com/regexp-samehost-1',
456  5,
457  ],
458  'regexp keep_query_parameters' => [
459  'https://acme.com/regexp-samehost-2',
460  'https://acme.com/',
461  200,
462  null,
463  null,
464  ],
465  'regexp keep_query_parameters - with query parameters' => [
466  'https://acme.com/regexp-samehost-2?param1=value1&cHash=feced69fa13ce7d3bf0483c21ff03064',
467  'https://acme.com/',
468  200,
469  null,
470  null,
471  ],
472  // this should redirect and not pass through
473  'regexp keep_query_parameters - with query parameters but without cHash' => [
474  'https://acme.com/regexp-samehost-2?param1=value1',
475  'https://acme.com/',
476  301,
477  'https://acme.com/regexp-samehost-2?param1=value1&cHash=feced69fa13ce7d3bf0483c21ff03064',
478  6,
479  ],
480  'regexp respect_query_parameters' => [
481  'https://acme.com/regexp-samehost-3',
482  'https://acme.com/',
483  200,
484  null,
485  null,
486  ],
487  // this should redirect and not pass through
488  'regexp respect_query_parameters - with query parameters but without cHash' => [
489  'https://acme.com/regexp-samehost-3?param1=value1',
490  'https://acme.com/',
491  301,
492  'https://acme.com/regexp-samehost-3',
493  7,
494  ],
495  'same host as external target with query arguments in another order than target should pass instead of redirect' => [
496  'https://acme.com/sanatize-samehost-3?param1=value1&param2=value2&param3=&cHash=69f1b01feb7ed14b95b85cbc66ee2a3a',
497  'https://acme.com/',
498  200,
499  null,
500  null,
501  ],
502  'same host as external target with fragment should pass instead of redirect' => [
503  'https://acme.com/sanatize-samehost-4',
504  'https://acme.com/',
505  200,
506  null,
507  null,
508  ],
509  ];
510  }
511 
516  public function ‪samePathWithSameDomainT3Target(string ‪$url, string $baseUri, int $expectedStatusCode, ?string $expectedRedirectUri, ?int $expectedRedirectUid): void
517  {
518  $this->importCSVDataSet(__DIR__ . '/Fixtures/RedirectService_samePathWithSameDomainT3Target.csv');
520  'acme-com',
521  $this->‪buildSiteConfiguration(1, $baseUri)
522  );
523  $this->setUpFrontendRootPage(
524  1,
525  ['EXT:redirects/Tests/Functional/Service/Fixtures/Redirects.typoscript']
526  );
527 
528  $response = $this->executeFrontendSubRequest(
529  new InternalRequest(‪$url),
530  null,
531  false
532  );
533  self::assertEquals($expectedStatusCode, $response->getStatusCode());
534  if ($expectedRedirectUri) {
535  self::assertIsArray($response->getHeader('X-Redirect-By'));
536  self::assertIsArray($response->getHeader('location'));
537  self::assertEquals('TYPO3 Redirect ' . $expectedRedirectUid, $response->getHeader('X-Redirect-By')[0]);
538  self::assertEquals($expectedRedirectUri, $response->getHeader('location')[0]);
539  }
540  }
541 
542  public static function ‪samePathWithSameDomainAndRelativeTargetDataProvider(): array
543  {
544  return [
545  'flat' => [
546  'https://acme.com/flat-samehost-1',
547  'https://acme.com/',
548  200,
549  null,
550  null,
551  ],
552  // this should redirect and not pass through
553  'flat - with query parameters' => [
554  'https://acme.com/flat-samehost-1?param1=value1&cHash=e0527192caa60a6dac1e30af7cfeaf64',
555  'https://acme.com/',
556  301,
557  '/flat-samehost-1',
558  1,
559  ],
560  'flat keep_query_parameters' => [
561  'https://acme.com/flat-samehost-2',
562  'https://acme.com/',
563  200,
564  null,
565  null,
566  ],
567  'flat keep_query_parameters - with query parameters' => [
568  'https://acme.com/flat-samehost-2?param1=value1&cHash=e0527192caa60a6dac1e30af7cfeaf64',
569  'https://acme.com/',
570  200,
571  null,
572  null,
573  ],
574  'flat respect_query_parameters' => [
575  'https://acme.com/flat-samehost-3',
576  'https://acme.com/',
577  200,
578  null,
579  null,
580  ],
581  // this should redirect and not pass through
582  'flat respect_query_parameters - with query parameters' => [
583  'https://acme.com/flat-samehost-3?param1=value1',
584  'https://acme.com/',
585  301,
586  '/flat-samehost-3',
587  3,
588  ],
589  'flat respect_query_parameters and keep_query_parameters' => [
590  'https://acme.com/flat-samehost-4',
591  'https://acme.com/',
592  200,
593  null,
594  null,
595  ],
596  'flat respect_query_parameters and keep_query_parameters - with query parameters' => [
597  'https://acme.com/flat-samehost-4?param1=value1&cHash=caa2156411affc2d7c8c5169652c6e13',
598  'https://acme.com/',
599  200,
600  null,
601  null,
602  ],
603  'regexp' => [
604  'https://acme.com/regexp-samehost-1',
605  'https://acme.com/',
606  200,
607  null,
608  null,
609  ],
610  // this should redirect and not pass through
611  'regexp - with query parameters' => [
612  'https://acme.com/regexp-samehost-1?param1=value1',
613  'https://acme.com/',
614  301,
615  '/regexp-samehost-1',
616  5,
617  ],
618  'regexp keep_query_parameters' => [
619  'https://acme.com/regexp-samehost-2',
620  'https://acme.com/',
621  200,
622  null,
623  null,
624  ],
625  'regexp keep_query_parameters - with query parameters' => [
626  'https://acme.com/regexp-samehost-2?param1=value1&cHash=feced69fa13ce7d3bf0483c21ff03064',
627  'https://acme.com/',
628  200,
629  null,
630  null,
631  ],
632  'regexp keep_query_parameters - with query parameters but without cHash' => [
633  'https://acme.com/regexp-samehost-2?param1=value1',
634  'https://acme.com/',
635  200,
636  null,
637  null,
638  ],
639  'regexp respect_query_parameters' => [
640  'https://acme.com/regexp-samehost-3',
641  'https://acme.com/',
642  200,
643  null,
644  null,
645  ],
646  // this should redirect and not pass through
647  'regexp respect_query_parameters - with query parameters but without cHash' => [
648  'https://acme.com/regexp-samehost-3?param1=value1',
649  'https://acme.com/',
650  301,
651  '/regexp-samehost-3',
652  7,
653  ],
654  ];
655  }
656 
661  public function ‪samePathWithSameDomainAndRelativeTarget(string ‪$url, string $baseUri, int $expectedStatusCode, ?string $expectedRedirectUri, ?int $expectedRedirectUid): void
662  {
663  $this->importCSVDataSet(__DIR__ . '/Fixtures/RedirectService_samePathWithSameDomainAndRelativeTarget.csv');
665  'acme-com',
666  $this->‪buildSiteConfiguration(1, $baseUri)
667  );
668  $this->setUpFrontendRootPage(
669  1,
670  ['EXT:redirects/Tests/Functional/Service/Fixtures/Redirects.typoscript']
671  );
672 
673  $response = $this->executeFrontendSubRequest(
674  new InternalRequest(‪$url),
675  null,
676  false
677  );
678  self::assertEquals($expectedStatusCode, $response->getStatusCode());
679  if ($expectedRedirectUri) {
680  self::assertIsArray($response->getHeader('X-Redirect-By'));
681  self::assertIsArray($response->getHeader('location'));
682  self::assertEquals('TYPO3 Redirect ' . $expectedRedirectUid, $response->getHeader('X-Redirect-By')[0]);
683  self::assertEquals($expectedRedirectUri, $response->getHeader('location')[0]);
684  }
685  }
686 
687  public static function ‪samePathRedirectsWithExternalTargetDataProvider(): array
688  {
689  return [
690  'flat' => [
691  'https://acme.com/flat-samehost-1',
692  'https://acme.com/',
693  301,
694  'https://external.acme.com/flat-samehost-1',
695  1,
696  ],
697  'flat - with query parameters' => [
698  'https://acme.com/flat-samehost-1?param1=value1&cHash=e0527192caa60a6dac1e30af7cfeaf64',
699  'https://acme.com/',
700  301,
701  'https://external.acme.com/flat-samehost-1',
702  1,
703  ],
704  'flat keep_query_parameters' => [
705  'https://acme.com/flat-samehost-2',
706  'https://acme.com/',
707  301,
708  'https://external.acme.com/flat-samehost-2',
709  2,
710  ],
711  'flat keep_query_parameters - with query parameters' => [
712  'https://acme.com/flat-samehost-2?param1=value1',
713  'https://acme.com/',
714  301,
715  'https://external.acme.com/flat-samehost-2?param1=value1',
716  2,
717  ],
718  // following will not match at all, so it is expected to be resolved with 200
719  'flat respect_query_parameters' => [
720  'https://acme.com/flat-samehost-3',
721  'https://acme.com/',
722  200,
723  null,
724  null,
725  ],
726  'flat respect_query_parameters - with query parameters' => [
727  'https://acme.com/flat-samehost-3?param1=value1',
728  'https://acme.com/',
729  301,
730  'https://external.acme.com/flat-samehost-3',
731  3,
732  ],
733  // following will not match at all, so it is expected to be resolved with 200
734  'flat respect_query_parameters and keep_query_parameters' => [
735  'https://acme.com/flat-samehost-4',
736  'https://acme.com/',
737  200,
738  null,
739  null,
740  ],
741  'flat respect_query_parameters and keep_query_parameters - with query parameters' => [
742  'https://acme.com/flat-samehost-4?param1=value1',
743  'https://acme.com/',
744  301,
745  'https://external.acme.com/flat-samehost-4?param1=value1',
746  4,
747  ],
748  'regexp' => [
749  'https://acme.com/regexp-samehost-1',
750  'https://acme.com/',
751  301,
752  'https://external.acme.com/regexp-samehost-1',
753  5,
754  ],
755  'regexp - with query parameters' => [
756  'https://acme.com/regexp-samehost-1?param1=value1',
757  'https://acme.com/',
758  301,
759  'https://external.acme.com/regexp-samehost-1',
760  5,
761  ],
762  'regexp keep_query_parameters' => [
763  'https://acme.com/regexp-samehost-2',
764  'https://acme.com/',
765  301,
766  'https://external.acme.com/regexp-samehost-2',
767  6,
768  ],
769  'regexp keep_query_parameters - with query parameters' => [
770  'https://acme.com/regexp-samehost-2?param1=value1',
771  'https://acme.com/',
772  301,
773  'https://external.acme.com/regexp-samehost-2?param1=value1',
774  6,
775  ],
776  'regexp respect_query_parameters' => [
777  'https://acme.com/regexp-samehost-3',
778  'https://acme.com/',
779  301,
780  'https://external.acme.com/regexp-samehost-3',
781  7,
782  ],
783  // this should redirect and not pass through
784  'regexp respect_query_parameters - with query parameters but without cHash' => [
785  'https://acme.com/regexp-samehost-3?param1=value1',
786  'https://acme.com/',
787  301,
788  'https://external.acme.com/regexp-samehost-3',
789  7,
790  ],
791  'same host as external target with port should pass instead of redirect' => [
792  'https://acme.com/sanatize-samehost-1',
793  'https://acme.com/',
794  200,
795  null,
796  null,
797  ],
798  'same host as external target with userinfo should pass instead of redirect' => [
799  'https://acme.com/sanatize-samehost-2',
800  'https://acme.com/',
801  200,
802  null,
803  null,
804  ],
805  'same host as external target with query arguments in another order than target should pass instead of redirect' => [
806  'https://acme.com/sanatize-samehost-3?param1=value1&param2=value2&param3=',
807  'https://acme.com/',
808  200,
809  null,
810  null,
811  ],
812  'same host as external target with fragment should pass instead of redirect' => [
813  'https://acme.com/sanatize-samehost-4',
814  'https://acme.com/',
815  200,
816  null,
817  null,
818  ],
819  ];
820  }
821 
826  public function ‪samePathRedirectsWithExternalTarget(string ‪$url, string $baseUri, int $expectedStatusCode, ?string $expectedRedirectUri, ?int $expectedRedirectUid): void
827  {
828  $this->importCSVDataSet(__DIR__ . '/Fixtures/RedirectService_samePathRedirectsWithExternalTarget.csv');
830  'acme-com',
831  $this->‪buildSiteConfiguration(1, $baseUri)
832  );
833  $this->setUpFrontendRootPage(
834  1,
835  ['EXT:redirects/Tests/Functional/Service/Fixtures/Redirects.typoscript']
836  );
837 
838  $response = $this->executeFrontendSubRequest(
839  new InternalRequest(‪$url),
840  null,
841  false
842  );
843  self::assertEquals($expectedStatusCode, $response->getStatusCode());
844  if ($expectedRedirectUri) {
845  self::assertIsArray($response->getHeader('X-Redirect-By'));
846  self::assertIsArray($response->getHeader('location'));
847  self::assertEquals('TYPO3 Redirect ' . $expectedRedirectUid, $response->getHeader('X-Redirect-By')[0]);
848  self::assertEquals($expectedRedirectUri, $response->getHeader('location')[0]);
849  }
850  }
851 
855  public function ‪beforeRedirectMatchDomainEventIsTriggered(): void
856  {
857  $this->importCSVDataSet(__DIR__ . '/Fixtures/BeforeRedirectMatchDomainEventIsTriggered.csv');
859  'acme-com',
860  $this->‪buildSiteConfiguration(1, 'https://acme.com/')
861  );
862 
863  $logger = new NullLogger();
864  $frontendUserAuthentication = new FrontendUserAuthentication();
865 
866  $siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
867  $uri = new Uri('https://acme.com/non-existing-page');
868  $request = ‪$GLOBALS['TYPO3_REQUEST'] = (new ServerRequest($uri))
869  ->withAttribute('site', $siteFinder->getSiteByRootPageId(1))
870  ->withAttribute('frontend.user', $frontendUserAuthentication)
871  ->withAttribute('applicationType', ‪SystemEnvironmentBuilder::REQUESTTYPE_FE);
872 
873  $dispatchedEvents = [];
875  $container = $this->getContainer();
876  $container->set(
877  'before-redirect-match-domain-event-is-triggered',
878  static function (BeforeRedirectMatchDomainEvent $event) use (
879  &$dispatchedEvents
880  ): void {
881  $dispatchedEvents[] = $event;
882  if ($event->getMatchDomainName() === '*') {
883  $event->setMatchedRedirect(['wildcard-manual-matched' => $event->getPath()]);
884  }
885  }
886  );
887 
888  $eventListener = $container->get(ListenerProvider::class);
889  $eventListener->addListener(BeforeRedirectMatchDomainEvent::class, 'before-redirect-match-domain-event-is-triggered');
890 
891  $redirectService = new RedirectService(
892  new RedirectCacheService(),
893  new LinkService(),
894  $siteFinder,
895  $this->get(EventDispatcherInterface::class),
896  );
897  $redirectService->setLogger($logger);
898 
899  $redirectMatch = $redirectService->matchRedirect($uri->getHost(), $uri->getPath(), $uri->getQuery());
900 
901  self::assertCount(2, $dispatchedEvents);
902  self::assertNull($dispatchedEvents[0]->getMatchedRedirect());
903  self::assertEquals(['wildcard-manual-matched' => $uri->getPath()], $dispatchedEvents[1]->getMatchedRedirect());
904  self::assertSame(['wildcard-manual-matched' => $uri->getPath()], $redirectMatch);
905  }
906 
908  {
909  // Regression test for https://forge.typo3.org/issues/101191
910  yield '#1 Non-query argument regex redirect not respecting get arguments before query-argument regex does not match before query-argument regex' => [
911  'dataSet' => __DIR__ . '/Fixtures/RegExp/case1.csv',
912  'url' => 'https://acme.com/foo/lightbar.html?type=101',
913  'statusCode' => 301,
914  'redirectUid' => 2,
915  'targetUrl' => 'https://acme.com/page3?type=101',
916  ];
917 
918  yield '#2 Non-query argument regex redirect respecting get arguments before query-argument regex does not match before query-argument regex' => [
919  'dataSet' => __DIR__ . '/Fixtures/RegExp/case2.csv',
920  'url' => 'https://acme.com/foo/lightbar.html?type=101',
921  'statusCode' => 301,
922  'redirectUid' => 2,
923  'targetUrl' => 'https://acme.com/page3?type=101',
924  ];
925 
926  // Redirect respecting query arguments but has a too open regexp provided and matching takes precedence over
927  // a later redirect with a "better" match. This is a configuration error and therefore the correct way to handle
928  // this case. For example missing trailing `$` or leaving the `respect_query_parameters` option unchecked would
929  // mitigate this.
930  yield '#3 To open non-query argument regex redirect respecting get arguments before query-argument regex proceeds query-argument regex' => [
931  'dataSet' => __DIR__ . '/Fixtures/RegExp/case3.csv',
932  'url' => 'https://acme.com/foo/lightbar.html?type=101',
933  'statusCode' => 301,
934  'redirectUid' => 1,
935  'targetUrl' => 'https://acme.com/page2',
936  ];
937  }
938 
944  string $importDataSet,
945  string ‪$url,
946  int ‪$statusCode,
947  int $redirectUid,
948  string ‪$targetUrl
949  ): void {
950  $this->importCSVDataSet($importDataSet);
952  'acme-com',
953  $this->‪buildSiteConfiguration(1, 'https://acme.com/')
954  );
955  $this->setUpFrontendRootPage(
956  1,
957  ['EXT:redirects/Tests/Functional/Service/Fixtures/Redirects.typoscript']
958  );
959 
960  $response = $this->executeFrontendSubRequest(
961  new InternalRequest(‪$url)
962  );
963  self::assertEquals(‪$statusCode, $response->getStatusCode());
964  self::assertIsArray($response->getHeader('X-Redirect-By'));
965  self::assertIsArray($response->getHeader('location'));
966  self::assertEquals('TYPO3 Redirect ' . $redirectUid, $response->getHeader('X-Redirect-By')[0]);
967  self::assertEquals(‪$targetUrl, $response->getHeader('location')[0]);
968  }
969 }
‪TYPO3\CMS\Redirects\Tests\Functional\Service\RedirectServiceTest\samePathRedirectsWithExternalTargetDataProvider
‪static samePathRedirectsWithExternalTargetDataProvider()
Definition: RedirectServiceTest.php:686
‪TYPO3\CMS\Redirects\Tests\Functional\Service\RedirectServiceTest\redirectsDataProvider
‪static redirectsDataProvider()
Definition: RedirectServiceTest.php:120
‪TYPO3\CMS\Redirects\Tests\Functional\Service\RedirectServiceTest\regexpWithNoParamRegexpAndRespectingGetParameteresRedirectsIfNoParamsAreGiven
‪regexpWithNoParamRegexpAndRespectingGetParameteresRedirectsIfNoParamsAreGiven()
Definition: RedirectServiceTest.php:353
‪TYPO3\CMS\Redirects\Tests\Functional\Service\RedirectServiceTest\samePathWithSameDomainT3TargetDataProvider
‪static samePathWithSameDomainT3TargetDataProvider()
Definition: RedirectServiceTest.php:381
‪TYPO3\CMS\Redirects\Tests\Functional\Service\RedirectServiceTest\linkForRedirectToAccessRestrictedPageIsBuild
‪linkForRedirectToAccessRestrictedPageIsBuild()
Definition: RedirectServiceTest.php:69
‪TYPO3\CMS\Redirects\Tests\Functional\Service\RedirectServiceTest\samePathWithSameDomainAndRelativeTargetDataProvider
‪static samePathWithSameDomainAndRelativeTargetDataProvider()
Definition: RedirectServiceTest.php:541
‪TYPO3\CMS\Redirects\Tests\Functional\Service\RedirectServiceTest\checkRegExpRedirects
‪checkRegExpRedirects(string $url, int $expectedStatusCode, string $expectedRedirectUri, int $expectedRedirectUid)
Definition: RedirectServiceTest.php:299
‪TYPO3\CMS\Core\Core\SystemEnvironmentBuilder
Definition: SystemEnvironmentBuilder.php:41
‪TYPO3\CMS\Core\Tests\Functional\SiteHandling\SiteBasedTestTrait
Definition: SiteBasedTestTrait.php:37
‪TYPO3\CMS\Redirects\Tests\Functional\Service\RedirectServiceTest\beforeRedirectMatchDomainEventIsTriggered
‪beforeRedirectMatchDomainEventIsTriggered()
Definition: RedirectServiceTest.php:854
‪TYPO3\CMS\Redirects\Event\BeforeRedirectMatchDomainEvent\getMatchDomainName
‪string getMatchDomainName()
Definition: BeforeRedirectMatchDomainEvent.php:65
‪TYPO3\CMS\Core\Tests\Functional\SiteHandling\SiteBasedTestTrait\writeSiteConfiguration
‪writeSiteConfiguration(string $identifier, array $site=[], array $languages=[], array $errorHandling=[])
Definition: SiteBasedTestTrait.php:50
‪TYPO3\CMS\Redirects\Service\RedirectService
Definition: RedirectService.php:51
‪TYPO3\CMS\Core\Site\SiteFinder
Definition: SiteFinder.php:31
‪TYPO3\CMS\Redirects\Message\$targetUrl
‪identifier readonly UriInterface $targetUrl
Definition: RedirectWasHitMessage.php:33
‪TYPO3\CMS\Core\Tests\Functional\SiteHandling\SiteBasedTestTrait\buildSiteConfiguration
‪buildSiteConfiguration(int $rootPageId, string $base='')
Definition: SiteBasedTestTrait.php:96
‪TYPO3\CMS\Redirects\Tests\Functional\Service\RedirectServiceTest\regExpRedirectsWithArgumentMatchesWithSimilarRegExpWithoutQueryParamInRecord
‪regExpRedirectsWithArgumentMatchesWithSimilarRegExpWithoutQueryParamInRecord(string $importDataSet, string $url, int $statusCode, int $redirectUid, string $targetUrl)
Definition: RedirectServiceTest.php:942
‪TYPO3\CMS\Redirects\Tests\Functional\Service\RedirectServiceTest\samePathRedirectsWithExternalTarget
‪samePathRedirectsWithExternalTarget(string $url, string $baseUri, int $expectedStatusCode, ?string $expectedRedirectUri, ?int $expectedRedirectUid)
Definition: RedirectServiceTest.php:825
‪TYPO3\CMS\Redirects\Tests\Functional\Service\RedirectServiceTest\$configurationToUseInTestInstance
‪array $configurationToUseInTestInstance
Definition: RedirectServiceTest.php:49
‪TYPO3\CMS\Core\Context\Context
Definition: Context.php:54
‪TYPO3\CMS\Core\Http\Uri
Definition: Uri.php:30
‪TYPO3\CMS\Redirects\Tests\Functional\Service\RedirectServiceTest\samePathWithSameDomainT3Target
‪samePathWithSameDomainT3Target(string $url, string $baseUri, int $expectedStatusCode, ?string $expectedRedirectUri, ?int $expectedRedirectUid)
Definition: RedirectServiceTest.php:515
‪TYPO3\CMS\Redirects\Service\RedirectCacheService
Definition: RedirectCacheService.php:34
‪TYPO3\CMS\Redirects\Tests\Functional\Service\RedirectServiceTest\LANGUAGE_PRESETS
‪const LANGUAGE_PRESETS
Definition: RedirectServiceTest.php:43
‪TYPO3\CMS\Redirects\Message\$statusCode
‪identifier readonly UriInterface readonly int $statusCode
Definition: RedirectWasHitMessage.php:34
‪TYPO3\CMS\Redirects\Tests\Functional\Service
Definition: IntegrityServiceTest.php:18
‪TYPO3\CMS\Redirects\Tests\Functional\Service\RedirectServiceTest\samePathWithSameDomainAndRelativeTarget
‪samePathWithSameDomainAndRelativeTarget(string $url, string $baseUri, int $expectedStatusCode, ?string $expectedRedirectUri, ?int $expectedRedirectUid)
Definition: RedirectServiceTest.php:660
‪TYPO3\CMS\Redirects\Tests\Functional\Service\RedirectServiceTest\checkRegExpRedirectsDataProvider
‪static checkRegExpRedirectsDataProvider()
Definition: RedirectServiceTest.php:172
‪TYPO3\CMS\Redirects\Tests\Functional\Service\RedirectServiceTest
Definition: RedirectServiceTest.php:41
‪TYPO3\CMS\Core\Http\ServerRequest
Definition: ServerRequest.php:39
‪TYPO3\CMS\Redirects\Tests\Functional\Service\RedirectServiceTest\checkReponseCodeOnRedirect
‪checkReponseCodeOnRedirect($url, $statusCode, $targetUrl, $redirectUid)
Definition: RedirectServiceTest.php:148
‪TYPO3\CMS\Webhooks\Message\$url
‪identifier readonly UriInterface $url
Definition: LoginErrorOccurredMessage.php:36
‪TYPO3\CMS\Redirects\Tests\Functional\Service\RedirectServiceTest\regexpWithNoParamRegexpAndRespectingGetParameteresIssuesNotFoundStatusIfParamsAreGivenInUrl
‪regexpWithNoParamRegexpAndRespectingGetParameteresIssuesNotFoundStatusIfParamsAreGivenInUrl()
Definition: RedirectServiceTest.php:327
‪TYPO3\CMS\Redirects\Tests\Functional\Service\RedirectServiceTest\setUp
‪setUp()
Definition: RedirectServiceTest.php:59
‪TYPO3\CMS\Core\EventDispatcher\NoopEventDispatcher
Definition: NoopEventDispatcher.php:29
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication
Definition: FrontendUserAuthentication.php:33
‪TYPO3\CMS\Redirects\Tests\Functional\Service\RedirectServiceTest\$coreExtensionsToLoad
‪array $coreExtensionsToLoad
Definition: RedirectServiceTest.php:47
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Core\SystemEnvironmentBuilder\REQUESTTYPE_FE
‪const REQUESTTYPE_FE
Definition: SystemEnvironmentBuilder.php:43
‪TYPO3\CMS\Core\EventDispatcher\ListenerProvider
Definition: ListenerProvider.php:30
‪TYPO3\CMS\Redirects\Event\BeforeRedirectMatchDomainEvent
Definition: BeforeRedirectMatchDomainEvent.php:28
‪TYPO3\CMS\Redirects\Tests\Functional\Service\RedirectServiceTest\regExpRedirectsWithArgumentMatchesWithSimilarRegExpWithoutQueryParamInRecordDataProvider
‪static regExpRedirectsWithArgumentMatchesWithSimilarRegExpWithoutQueryParamInRecordDataProvider()
Definition: RedirectServiceTest.php:906