[BUGFIX] Make redirect sources with Query matchable
[Packages/TYPO3.CMS.git] / typo3 / sysext / redirects / Tests / Unit / Service / RedirectServiceTest.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Redirects\Tests\Unit\Service;
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 Prophecy\Prophecy\ObjectProphecy;
20 use Psr\Log\LoggerInterface;
21 use TYPO3\CMS\Core\Http\Uri;
22 use TYPO3\CMS\Core\LinkHandling\LinkService;
23 use TYPO3\CMS\Core\Resource\Exception\InvalidPathException;
24 use TYPO3\CMS\Core\Resource\File;
25 use TYPO3\CMS\Core\Resource\Folder;
26 use TYPO3\CMS\Core\Utility\GeneralUtility;
27 use TYPO3\CMS\Redirects\Service\RedirectCacheService;
28 use TYPO3\CMS\Redirects\Service\RedirectService;
29 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
30
31 class RedirectServiceTest extends UnitTestCase
32 {
33 /**
34 * @var bool Reset singletons created by subject
35 */
36 protected $resetSingletonInstances = true;
37
38 /**
39 * @var RedirectCacheService|ObjectProphecy
40 */
41 protected $redirectCacheServiceProphecy;
42
43 /**
44 * @var RedirectService
45 */
46 protected $redirectService;
47
48 protected function setUp()
49 {
50 parent::setUp();
51 $loggerProphecy = $this->prophesize(LoggerInterface::class);
52 $this->redirectCacheServiceProphecy = $this->prophesize(RedirectCacheService::class);
53 $this->redirectService = new RedirectService();
54 $this->redirectService->setLogger($loggerProphecy->reveal());
55
56 $GLOBALS['SIM_ACCESS_TIME'] = 42;
57 }
58
59 /**
60 * @test
61 */
62 public function matchRedirectReturnsNullIfNoRedirectsExist()
63 {
64 $this->redirectCacheServiceProphecy->getRedirects()->willReturn([]);
65 GeneralUtility::addInstance(RedirectCacheService::class, $this->redirectCacheServiceProphecy->reveal());
66
67 $result = $this->redirectService->matchRedirect('example.com', 'foo');
68
69 self::assertNull($result);
70 }
71
72 /**
73 * @test
74 */
75 public function matchRedirectReturnsRedirectOnFlatMatch()
76 {
77 $row = [
78 'target' => 'https://example.com',
79 'force_https' => '0',
80 'keep_query_parameters' => '0',
81 'target_statuscode' => '307',
82 'disabled' => '0',
83 'starttime' => '0',
84 'endtime' => '0'
85 ];
86 $this->redirectCacheServiceProphecy->getRedirects()->willReturn(
87 [
88 'example.com' => [
89 'flat' => [
90 'foo/' => [
91 1 => $row,
92 ],
93 ],
94 ],
95 ]
96 );
97 GeneralUtility::addInstance(RedirectCacheService::class, $this->redirectCacheServiceProphecy->reveal());
98
99 $result = $this->redirectService->matchRedirect('example.com', 'foo');
100
101 self::assertSame($row, $result);
102 }
103
104 /**
105 * @test
106 */
107 public function matchRedirectReturnsRedirectOnRespectQueryParametersMatch()
108 {
109 $row = [
110 'target' => 'https://example.com',
111 'force_https' => '0',
112 'keep_query_parameters' => '0',
113 'respect_query_parameters' => '1',
114 'target_statuscode' => '307',
115 'disabled' => '0',
116 'starttime' => '0',
117 'endtime' => '0'
118 ];
119 $this->redirectCacheServiceProphecy->getRedirects()->willReturn(
120 [
121 'example.com' => [
122 'respect_query_parameters' => [
123 'index.php?id=123' => [
124 1 => $row,
125 ],
126 ],
127 ],
128 ]
129 );
130 GeneralUtility::addInstance(RedirectCacheService::class, $this->redirectCacheServiceProphecy->reveal());
131
132 $result = $this->redirectService->matchRedirect('example.com', 'index.php', 'id=123');
133
134 self::assertSame($row, $result);
135 }
136
137 /**
138 * @test
139 */
140 public function matchRedirectReturnsRedirectOnRespectQueryParametersMatchWithSlash()
141 {
142 $row = [
143 'target' => 'https://example.com',
144 'force_https' => '0',
145 'keep_query_parameters' => '0',
146 'respect_query_parameters' => '1',
147 'target_statuscode' => '307',
148 'disabled' => '0',
149 'starttime' => '0',
150 'endtime' => '0'
151 ];
152 $this->redirectCacheServiceProphecy->getRedirects()->willReturn(
153 [
154 'example.com' => [
155 'respect_query_parameters' => [
156 'index.php/?id=123' => [
157 1 => $row,
158 ],
159 ],
160 ],
161 ]
162 );
163 GeneralUtility::addInstance(RedirectCacheService::class, $this->redirectCacheServiceProphecy->reveal());
164
165 $result = $this->redirectService->matchRedirect('example.com', 'index.php', 'id=123');
166
167 self::assertSame($row, $result);
168 }
169
170 /**
171 * @test
172 */
173 public function matchRedirectReturnsRedirectOnFullRespectQueryParametersMatch()
174 {
175 $row = [
176 'target' => 'https://example.com/target',
177 'force_https' => '0',
178 'keep_query_parameters' => '0',
179 'respect_query_parameters' => '1',
180 'target_statuscode' => '307',
181 'disabled' => '0',
182 'starttime' => '0',
183 'endtime' => '0'
184 ];
185 $this->redirectCacheServiceProphecy->getRedirects()->willReturn(
186 [
187 'example.com' => [
188 'respect_query_parameters' => [
189 'index.php?id=123&a=b' => [
190 1 => $row,
191 ],
192 ],
193 ],
194 ]
195 );
196 GeneralUtility::addInstance(RedirectCacheService::class, $this->redirectCacheServiceProphecy->reveal());
197
198 $result = $this->redirectService->matchRedirect('example.com', 'index.php', 'id=123&a=b');
199
200 self::assertSame($row, $result);
201 }
202
203 /**
204 * @test
205 */
206 public function matchRedirectReturnsNullOnPartialRespectQueryParametersMatch()
207 {
208 $row = [
209 'target' => 'https://example.com/target',
210 'force_https' => '0',
211 'keep_query_parameters' => '0',
212 'respect_query_parameters' => '1',
213 'target_statuscode' => '307',
214 'disabled' => '0',
215 'starttime' => '0',
216 'endtime' => '0'
217 ];
218 $this->redirectCacheServiceProphecy->getRedirects()->willReturn(
219 [
220 'example.com' => [
221 'respect_query_parameters' => [
222 'index.php?id=123&a=b' => [
223 1 => $row,
224 ],
225 ],
226 ],
227 ]
228 );
229 GeneralUtility::addInstance(RedirectCacheService::class, $this->redirectCacheServiceProphecy->reveal());
230
231 $result = $this->redirectService->matchRedirect('example.com', 'index.php', 'id=123&a=a');
232
233 self::assertSame(null, $result);
234 }
235
236 /**
237 * @test
238 */
239 public function matchRedirectReturnsMatchingRedirectWithMatchingQueryParametersOverMatchingPath()
240 {
241 $row1 = [
242 'target' => 'https://example.com/no-promotion',
243 'force_https' => '0',
244 'keep_query_parameters' => '0',
245 'respect_query_parameters' => '0',
246 'target_statuscode' => '307',
247 'disabled' => '0',
248 'starttime' => '0',
249 'endtime' => '0'
250 ];
251 $row2 = [
252 'target' => 'https://example.com/promotion',
253 'force_https' => '0',
254 'keep_query_parameters' => '0',
255 'respect_query_parameters' => '1',
256 'target_statuscode' => '307',
257 'disabled' => '0',
258 'starttime' => '0',
259 'endtime' => '0'
260 ];
261 $this->redirectCacheServiceProphecy->getRedirects()->willReturn(
262 [
263 'example.com' => [
264 'flat' => [
265 'special/page/' =>
266 [
267 1 => $row1,
268 ]
269 ],
270 'respect_query_parameters' => [
271 'special/page?key=998877' => [
272 1 => $row2,
273 ],
274 ],
275 ],
276 ]
277 );
278 GeneralUtility::addInstance(RedirectCacheService::class, $this->redirectCacheServiceProphecy->reveal());
279
280 $result = $this->redirectService->matchRedirect('example.com', 'special/page', 'key=998877');
281
282 self::assertSame($row2, $result);
283 }
284
285 /**
286 * @test
287 */
288 public function matchRedirectReturnsRedirectSpecificToDomainOnFlatMatchIfSpecificAndNonSpecificExist()
289 {
290 $row1 = [
291 'target' => 'https://example.com',
292 'force_https' => '0',
293 'keep_query_parameters' => '0',
294 'target_statuscode' => '307',
295 'disabled' => '0',
296 'starttime' => '0',
297 'endtime' => '0'
298 ];
299 $row2 = [
300 'target' => 'https://example.net',
301 'force_https' => '0',
302 'keep_query_parameters' => '0',
303 'target_statuscode' => '307',
304 'disabled' => '0',
305 'starttime' => '0',
306 'endtime' => '0'
307 ];
308 $this->redirectCacheServiceProphecy->getRedirects()->willReturn(
309 [
310 'example.com' => [
311 'flat' => [
312 'foo/' => [
313 1 => $row1,
314 ],
315 ],
316 ],
317 '*' => [
318 'flat' => [
319 'foo/' => [
320 2 => $row2,
321 ],
322 ],
323 ],
324 ]
325 );
326 GeneralUtility::addInstance(RedirectCacheService::class, $this->redirectCacheServiceProphecy->reveal());
327
328 $result = $this->redirectService->matchRedirect('example.com', 'foo');
329
330 self::assertSame($row1, $result);
331 }
332
333 /**
334 * @test
335 */
336 public function matchRedirectReturnsRedirectOnRegexMatch()
337 {
338 $row = [
339 'target' => 'https://example.com',
340 'force_https' => '0',
341 'keep_query_parameters' => '0',
342 'target_statuscode' => '307',
343 'disabled' => '0',
344 'starttime' => '0',
345 'endtime' => '0'
346 ];
347 $this->redirectCacheServiceProphecy->getRedirects()->willReturn(
348 [
349 'example.com' => [
350 'regexp' => [
351 '/f.*?/' => [
352 1 => $row,
353 ],
354 ],
355 ],
356 ]
357 );
358 GeneralUtility::addInstance(RedirectCacheService::class, $this->redirectCacheServiceProphecy->reveal());
359
360 $result = $this->redirectService->matchRedirect('example.com', 'foo');
361
362 self::assertSame($row, $result);
363 }
364
365 /**
366 * @test
367 */
368 public function matchRedirectReturnsOnlyActiveRedirects()
369 {
370 $row1 = [
371 'target' => 'https://example.com',
372 'force_https' => '0',
373 'keep_query_parameters' => '0',
374 'target_statuscode' => '307',
375 'starttime' => '0',
376 'endtime' => '0',
377 'disabled' => '1'
378 ];
379 $row2 = [
380 'target' => 'https://example.net',
381 'force_https' => '0',
382 'keep_query_parameters' => '0',
383 'target_statuscode' => '307',
384 'starttime' => '0',
385 'endtime' => '0',
386 'disabled' => '0'
387 ];
388 $this->redirectCacheServiceProphecy->getRedirects()->willReturn(
389 [
390 'example.com' => [
391 'flat' => [
392 'foo/' => [
393 1 => $row1,
394 2 => $row2
395 ],
396 ],
397 ],
398 ]
399 );
400 GeneralUtility::addInstance(RedirectCacheService::class, $this->redirectCacheServiceProphecy->reveal());
401
402 $result = $this->redirectService->matchRedirect('example.com', 'foo');
403
404 self::assertSame($row2, $result);
405 }
406
407 /**
408 * @test
409 */
410 public function getTargetUrlReturnsNullIfUrlCouldNotBeResolved()
411 {
412 $linkServiceProphecy = $this->prophesize(LinkService::class);
413 $linkServiceProphecy->resolve(Argument::any())->willThrow(new InvalidPathException('', 1516531195));
414 GeneralUtility::setSingletonInstance(LinkService::class, $linkServiceProphecy->reveal());
415
416 $result = $this->redirectService->getTargetUrl(['target' => 'invalid'], []);
417
418 self::assertNull($result);
419 }
420
421 /**
422 * @test
423 */
424 public function getTargetUrlReturnsUrlForTypeUrl()
425 {
426 $linkServiceProphecy = $this->prophesize(LinkService::class);
427 $redirectTargetMatch = [
428 'target' => 'https://example.com',
429 'force_https' => '0',
430 'keep_query_parameters' => '0'
431 ];
432 $linkDetails = [
433 'type' => LinkService::TYPE_URL,
434 'url' => 'https://example.com/'
435 ];
436 $linkServiceProphecy->resolve($redirectTargetMatch['target'])->willReturn($linkDetails);
437 GeneralUtility::setSingletonInstance(LinkService::class, $linkServiceProphecy->reveal());
438
439 $result = $this->redirectService->getTargetUrl($redirectTargetMatch, []);
440
441 $uri = new Uri('https://example.com/');
442 self::assertEquals($uri, $result);
443 }
444
445 /**
446 * @test
447 */
448 public function getTargetUrlReturnsUrlForTypeFile()
449 {
450 $linkServiceProphecy = $this->prophesize(LinkService::class);
451 $fileProphecy = $this->prophesize(File::class);
452 $fileProphecy->getPublicUrl()->willReturn('https://example.com/file.txt');
453 $redirectTargetMatch = [
454 'target' => 'https://example.com',
455 'force_https' => '0',
456 'keep_query_parameters' => '0',
457 ];
458 $linkDetails = [
459 'type' => LinkService::TYPE_FILE,
460 'file' => $fileProphecy->reveal()
461 ];
462 $linkServiceProphecy->resolve($redirectTargetMatch['target'])->willReturn($linkDetails);
463 GeneralUtility::setSingletonInstance(LinkService::class, $linkServiceProphecy->reveal());
464
465 $result = $this->redirectService->getTargetUrl($redirectTargetMatch, []);
466
467 $uri = new Uri('https://example.com/file.txt');
468 self::assertEquals($uri, $result);
469 }
470
471 /**
472 * @test
473 */
474 public function getTargetUrlReturnsUrlForTypeFolder()
475 {
476 $linkServiceProphecy = $this->prophesize(LinkService::class);
477 $folderProphecy = $this->prophesize(Folder::class);
478 $folderProphecy->getPublicUrl()->willReturn('https://example.com/folder/');
479 $redirectTargetMatch = [
480 'target' => 'https://example.com',
481 'force_https' => '0',
482 'keep_query_parameters' => '0',
483 ];
484 $folder = $folderProphecy->reveal();
485 $linkDetails = [
486 'type' => LinkService::TYPE_FOLDER,
487 'folder' => $folder
488 ];
489 $linkServiceProphecy->resolve($redirectTargetMatch['target'])->willReturn($linkDetails);
490 GeneralUtility::setSingletonInstance(LinkService::class, $linkServiceProphecy->reveal());
491
492 $result = $this->redirectService->getTargetUrl($redirectTargetMatch, []);
493
494 $uri = new Uri('https://example.com/folder/');
495 self::assertEquals($uri, $result);
496 }
497
498 /**
499 * @test
500 */
501 public function getTargetUrlRespectsForceHttps()
502 {
503 $linkServiceProphecy = $this->prophesize(LinkService::class);
504 $redirectTargetMatch = [
505 'target' => 'https://example.com',
506 'keep_query_parameters' => '0',
507 'force_https' => '1',
508 ];
509 $linkDetails = [
510 'type' => LinkService::TYPE_URL,
511 'url' => 'http://example.com'
512 ];
513 $linkServiceProphecy->resolve($redirectTargetMatch['target'])->willReturn($linkDetails);
514 GeneralUtility::setSingletonInstance(LinkService::class, $linkServiceProphecy->reveal());
515
516 $result = $this->redirectService->getTargetUrl($redirectTargetMatch, []);
517
518 $uri = new Uri('https://example.com');
519 self::assertEquals($uri, $result);
520 }
521
522 /**
523 * @test
524 */
525 public function getTargetUrlAddsExistingQueryParams()
526 {
527 $linkServiceProphecy = $this->prophesize(LinkService::class);
528 $redirectTargetMatch = [
529 'target' => 'https://example.com',
530 'force_https' => '0',
531 'keep_query_parameters' => '1'
532 ];
533 $linkDetails = [
534 'type' => LinkService::TYPE_URL,
535 'url' => 'https://example.com/?foo=1&bar=2'
536 ];
537 $linkServiceProphecy->resolve($redirectTargetMatch['target'])->willReturn($linkDetails);
538 GeneralUtility::setSingletonInstance(LinkService::class, $linkServiceProphecy->reveal());
539
540 $result = $this->redirectService->getTargetUrl($redirectTargetMatch, ['bar' => 3, 'baz' => 4]);
541
542 $uri = new Uri('https://example.com/?bar=2&baz=4&foo=1');
543 self::assertEquals($uri, $result);
544 }
545 }