‪TYPO3CMS  11.5
ExternalLinktypeTest.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 GuzzleHttp\Cookie\CookieJar;
21 use GuzzleHttp\Exception\ClientException;
22 use GuzzleHttp\Psr7\Response;
23 use Prophecy\Argument;
24 use Prophecy\PhpUnit\ProphecyTrait;
25 use Prophecy\Prophecy\ObjectProphecy;
31 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
32 
33 class ‪ExternalLinktypeTest extends UnitTestCase
34 {
35  use ProphecyTrait;
36 
37  protected function ‪setUp(): void
38  {
39  parent::setUp();
40  ‪$GLOBALS['LANG'] = $this->‪buildLanguageServiceProphecy()->reveal();
41  }
42 
43  private function ‪buildLanguageServiceProphecy(): ObjectProphecy
44  {
45  $languageServiceProphecy = $this->prophesize(LanguageService::class);
46  $languageServiceProphecy
47  ->includeLLFile('EXT:linkvalidator/Resources/Private/Language/Module/locallang.xlf');
48  $languageServiceProphecy->getLL(Argument::any())->willReturn('translation string');
49  return $languageServiceProphecy;
50  }
51 
56  {
57  $responseProphecy = $this->prophesize(Response::class);
58  $responseProphecy->getStatusCode()->willReturn(404);
59 
60  $exceptionProphecy = $this->prophesize(ClientException::class);
61  $exceptionProphecy->hasResponse()
62  ->willReturn(true);
63  $exceptionProphecy->getResponse()
64  ->willReturn($responseProphecy->reveal());
65 
66  $url = 'https://example.org/~not-existing-url';
67  $options = $this->‪getRequestHeaderOptions();
68  $requestFactoryProphecy = $this->prophesize(RequestFactory::class);
69  $requestFactoryProphecy->request($url, 'HEAD', $options)
70  ->willThrow($exceptionProphecy->reveal());
71 
72  $optionsSecondTryWithGET = array_merge_recursive($options, ['headers' => ['Range' => 'bytes=0-4048']]);
73  $requestFactoryProphecy->request($url, 'GET', $optionsSecondTryWithGET)
74  ->willThrow($exceptionProphecy->reveal());
75  $subject = new ‪ExternalLinktype($requestFactoryProphecy->reveal());
76 
77  $result = $subject->checkLink($url, null, null);
78 
79  self::assertFalse($result);
80  }
81 
86  {
87  $responseProphecy = $this->prophesize(Response::class);
88  $responseProphecy->getStatusCode()->willReturn(404);
89 
90  $exceptionProphecy = $this->prophesize(ClientException::class);
91  $exceptionProphecy->hasResponse()
92  ->willReturn(true);
93  $exceptionProphecy->getResponse()
94  ->willReturn($responseProphecy->reveal());
95 
96  $options = $this->‪getRequestHeaderOptions();
97 
98  $url = 'https://example.org/~not-existing-url';
99  $requestFactoryProphecy = $this->prophesize(RequestFactory::class);
100  $requestFactoryProphecy->request($url, 'HEAD', $options)
101  ->willThrow($exceptionProphecy->reveal());
102  $optionsSecondTryWithGET = array_merge_recursive($options, ['headers' => ['Range' => 'bytes=0-4048']]);
103  $requestFactoryProphecy->request($url, 'GET', $optionsSecondTryWithGET)
104  ->willThrow($exceptionProphecy->reveal());
105  $subject = new ExternalLinktype($requestFactoryProphecy->reveal());
106 
107  $subject->checkLink($url, null, null);
108  $errorParams = $subject->getErrorParams();
109 
110  self::assertSame($errorParams['errorType'], 'httpStatusCode');
111  self::assertSame($errorParams['errno'], 404);
112  }
113 
114  private function ‪getCookieJarProphecy(): CookieJar
115  {
116  $cookieJar = $this->prophesize(CookieJar::class);
117  $cookieJar = $cookieJar->reveal();
118  GeneralUtility::addInstance(CookieJar::class, $cookieJar);
119  return $cookieJar;
120  }
121 
122  private function ‪getRequestHeaderOptions(): array
123  {
124  return [
125  'cookies' => $this->‪getCookieJarProphecy(),
126  'allow_redirects' => ['strict' => true],
127  'headers' => [
128  'User-Agent' => 'TYPO3 linkvalidator',
129  'Accept' => '*/*',
130  'Accept-Language' => '*',
131  'Accept-Encoding' => '*',
132  ],
133  ];
134  }
135 
136  public function ‪preprocessUrlsDataProvider(): \Generator
137  {
138  // regression test for issue #92230: handle incomplete or faulty URLs gracefully
139  yield 'faulty URL with mailto' => [
140  'mailto:http://example.org',
141  'mailto:http://example.org',
142  ];
143  yield 'Relative URL' => [
144  '/abc',
145  '/abc',
146  ];
147 
148  // regression tests for issues #89488, #89682
149  yield 'URL with query parameter and ampersand' => [
150  'https://standards.cen.eu/dyn/www/f?p=204:6:0::::FSP_ORG_ID,FSP_LANG_ID:,22&cs=1A3FFBC44FAB6B2A181C9525249C3A829',
151  'https://standards.cen.eu/dyn/www/f?p=204:6:0::::FSP_ORG_ID,FSP_LANG_ID:,22&cs=1A3FFBC44FAB6B2A181C9525249C3A829',
152  ];
153  yield 'URL with query parameter and ampersand with HTML entities' => [
154  'https://standards.cen.eu/dyn/www/f?p=204:6:0::::FSP_ORG_ID,FSP_LANG_ID:,22&amp;cs=1A3FFBC44FAB6B2A181C9525249C3A829',
155  'https://standards.cen.eu/dyn/www/f?p=204:6:0::::FSP_ORG_ID,FSP_LANG_ID:,22&cs=1A3FFBC44FAB6B2A181C9525249C3A829',
156  ];
157 
158  // regression tests for #89378
159  yield 'URL with path with dashes' => [
160  'https://example.com/Unternehmen/Ausbildung-Qualifikation/Weiterbildung-in-Niedersachsen/',
161  'https://example.com/Unternehmen/Ausbildung-Qualifikation/Weiterbildung-in-Niedersachsen/',
162  ];
163  yield 'URL with path with dashes (2)' => [
164  'https://example.com/startseite/wirtschaft/wirtschaftsfoerderung/beratung-foerderung/gruenderberatung/gruenderforen.html',
165  'https://example.com/startseite/wirtschaft/wirtschaftsfoerderung/beratung-foerderung/gruenderberatung/gruenderforen.html',
166  ];
167  yield 'URL with path with dashes (3)' => [
168  'http://example.com/universitaet/die-uni-im-ueberblick/lageplan/gebaeude/building/120',
169  'http://example.com/universitaet/die-uni-im-ueberblick/lageplan/gebaeude/building/120',
170  ];
171  yield 'URL with path and query parameters (including &, ~,; etc.)' => [
172  'http://example.com/tv?bcpid=1701167454001&amp;amp;amp;bckey=AQ~~,AAAAAGL7LqU~,aXlKNnCf9d9Tmck-kOc4PGFfCgHjM5JR&amp;amp;amp;bctid=1040702768001',
173  'http://example.com/tv?bcpid=1701167454001&amp;amp;bckey=AQ~~,AAAAAGL7LqU~,aXlKNnCf9d9Tmck-kOc4PGFfCgHjM5JR&amp;amp;bctid=1040702768001',
174  ];
175 
176  // make sure we correctly handle URLs with query parameters and fragment etc.
177  yield 'URL with query parameters, fragment, user, pass, port etc.' => [
178  'http://usr:pss@example.com:81/mypath/myfile.html?a=b&b[]=2&b[]=3#myfragment',
179  'http://usr:pss@example.com:81/mypath/myfile.html?a=b&b[]=2&b[]=3#myfragment',
180  ];
181  yield 'domain with special characters, URL with query parameters, fragment, user, pass, port etc.' => [
182  'http://usr:pss@äxample.com:81/mypath/myfile.html?a=b&b[]=2&b[]=3#myfragment',
183  'http://usr:pss@xn--xample-9ta.com:81/mypath/myfile.html?a=b&b[]=2&b[]=3#myfragment',
184  ];
185 
186  // domains with special characters: should be converted to punycode
187  yield 'domain with special characters' => [
188  'https://www.grün-example.org',
189  'https://www.xn--grn-example-uhb.org',
190  ];
191  yield 'domain with special characters and path' => [
192  'https://www.grün-example.org/a/bcd-efg/sfsfsfsfsf',
193  'https://www.xn--grn-example-uhb.org/a/bcd-efg/sfsfsfsfsf',
194  ];
195  }
196 
201  public function ‪preprocessUrlReturnsCorrectString(string $inputUrl, $expectedResult): void
202  {
203  $subject = new ‪ExternalLinktype();
204  $method = new \ReflectionMethod($subject, 'preprocessUrl');
205  $method->setAccessible(true);
206  $result = $method->invokeArgs($subject, [$inputUrl]);
207  self::assertEquals($result, $expectedResult);
208  }
209 
213  public function ‪setAdditionalConfigMergesHeaders(): void
214  {
215  $requestFactoryProphecy = $this->prophesize(RequestFactory::class);
216  $requestFactoryProphecy->request('http://example.com', 'HEAD', Argument::any())->shouldBeCalled();
217 
218  $externalLinkType = new ExternalLinktype($requestFactoryProphecy->reveal());
219  $externalLinkType->setAdditionalConfig(['headers.' => [
220  'X-MAS' => 'Merry!',
221  ]]);
222 
223  $externalLinkType->checkLink('http://example.com', [], $this->prophesize(LinkAnalyzer::class)->reveal());
224 
225  $requestFactoryProphecy->request('http://example.com', 'HEAD', Argument::that(static function ($result) {
226  return $result['headers']['X-MAS'] === 'Merry!' && $result['headers']['User-Agent'] === 'TYPO3 linkvalidator';
227  }))->shouldBeCalled();
228  }
229 
236  {
237  $requestFactoryProphecy = $this->prophesize(RequestFactory::class);
238  $externalLinkType = new ExternalLinktype($requestFactoryProphecy->reveal());
239  $externalLinkType->setAdditionalConfig([]);
240  $requestFactoryProphecy->request('http://example.com', 'HEAD', Argument::that(static function ($result) {
241  if (isset($result['timeout'])) {
242  return false;
243  }
244  return true;
245  }))->shouldBeCalled();
246  $externalLinkType->checkLink('http://example.com', [], $this->prophesize(LinkAnalyzer::class)->reveal());
247  }
248 
252  public function ‪setAdditionalConfigOverwritesUserAgent(): void
253  {
254  $requestFactoryProphecy = $this->prophesize(RequestFactory::class);
255  $requestFactoryProphecy->request('http://example.com', 'HEAD', Argument::any())->shouldBeCalled();
256 
257  $externalLinktype = new ExternalLinktype($requestFactoryProphecy->reveal());
258  $externalLinktype->setAdditionalConfig([
259  'httpAgentName' => 'TYPO3 Testing',
260  ]);
261 
262  $externalLinktype->checkLink('http://example.com', [], $this->prophesize(LinkAnalyzer::class)->reveal());
263 
264  $requestFactoryProphecy->request('http://example.com', 'HEAD', Argument::that(static function ($result) {
265  return $result['headers']['User-Agent'] === 'TYPO3 Testing';
266  }))->shouldBeCalled();
267  }
268 
273  {
274  $requestFactoryProphecy = $this->prophesize(RequestFactory::class);
275  $requestFactoryProphecy->request('http://example.com', 'HEAD', Argument::any())->shouldBeCalled();
276 
277  $externalLinkType = new ExternalLinktype($requestFactoryProphecy->reveal());
278  $externalLinkType->setAdditionalConfig([
279  'httpAgentUrl' => 'http://example.com',
280  ]);
281 
282  $externalLinkType->checkLink('http://example.com', [], $this->prophesize(LinkAnalyzer::class)->reveal());
283 
284  $requestFactoryProphecy->request('http://example.com', 'HEAD', Argument::that(static function ($result) {
285  return $result['headers']['User-Agent'] === 'TYPO3 linkvalidator http://example.com';
286  }))->shouldBeCalled();
287  }
288 
292  public function ‪setAdditionalConfigAppendsEmailIfConfigured(): void
293  {
294  $requestFactoryProphecy = $this->prophesize(RequestFactory::class);
295  $requestFactoryProphecy->request('http://example.com', 'HEAD', Argument::any())->shouldBeCalled();
296 
297  $externalLinktype = new ExternalLinktype($requestFactoryProphecy->reveal());
298  $externalLinktype->setAdditionalConfig([
299  'httpAgentEmail' => 'mail@example.com',
300  ]);
301 
302  $externalLinktype->checkLink('http://example.com', [], $this->prophesize(LinkAnalyzer::class)->reveal());
303 
304  $requestFactoryProphecy->request('http://example.com', 'HEAD', Argument::that(static function ($result) {
305  return $result['headers']['User-Agent'] === 'TYPO3 linkvalidator;mail@example.com';
306  }))->shouldBeCalled();
307  }
308 
313  {
314  $requestFactoryProphecy = $this->prophesize(RequestFactory::class);
315  $requestFactoryProphecy->request('http://example.com', 'HEAD', Argument::any())->shouldBeCalled();
316  ‪$GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress'] = 'test@example.com';
317 
318  $externalLinkType = new ExternalLinktype($requestFactoryProphecy->reveal());
319  $externalLinkType->setAdditionalConfig([]);
320 
321  $externalLinkType->checkLink('http://example.com', [], $this->prophesize(LinkAnalyzer::class)->reveal());
322 
323  $requestFactoryProphecy->request('http://example.com', 'HEAD', Argument::that(static function ($result) {
324  return $result['headers']['User-Agent'] === 'TYPO3 linkvalidator;test@example.com';
325  }))->shouldBeCalled();
326  }
327 
331  public function ‪setAdditionalConfigSetsRangeAndMethod(): void
332  {
333  $requestFactoryProphecy = $this->prophesize(RequestFactory::class);
334  $requestFactoryProphecy->request('http://example.com', 'GET', Argument::any())->shouldBeCalled();
335  ‪$GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress'] = 'test@example.com';
336 
337  $externalLinkType = new ExternalLinktype($requestFactoryProphecy->reveal());
338  $externalLinkType->setAdditionalConfig([
339  'method' => 'GET',
340  'range' => '0-2048',
341  ]);
342 
343  $externalLinkType->checkLink('http://example.com', [], $this->prophesize(LinkAnalyzer::class)->reveal());
344 
345  $requestFactoryProphecy->request('http://example.com', 'GET', Argument::that(static function ($result) {
346  return $result['headers']['Range'] === 'bytes=0-2048';
347  }))->shouldBeCalled();
348  }
349 }
‪TYPO3\CMS\Linkvalidator\Tests\Unit\Linktype\ExternalLinktypeTest\setAdditionalConfigMergesHeaders
‪setAdditionalConfigMergesHeaders()
Definition: ExternalLinktypeTest.php:212
‪TYPO3\CMS\Linkvalidator\Tests\Unit\Linktype\ExternalLinktypeTest\preprocessUrlsDataProvider
‪preprocessUrlsDataProvider()
Definition: ExternalLinktypeTest.php:135
‪TYPO3\CMS\Linkvalidator\Tests\Unit\Linktype\ExternalLinktypeTest\setAdditionalConfigOverwritesUserAgent
‪setAdditionalConfigOverwritesUserAgent()
Definition: ExternalLinktypeTest.php:251
‪TYPO3\CMS\Linkvalidator\Tests\Unit\Linktype\ExternalLinktypeTest\checkLinkWithExternalUrlNotFoundResultsNotFoundErrorType
‪checkLinkWithExternalUrlNotFoundResultsNotFoundErrorType()
Definition: ExternalLinktypeTest.php:84
‪TYPO3\CMS\Linkvalidator\Tests\Unit\Linktype\ExternalLinktypeTest\requestWithNoTimeoutIsCalledIfTimeoutNotSetByTsConfig
‪requestWithNoTimeoutIsCalledIfTimeoutNotSetByTsConfig()
Definition: ExternalLinktypeTest.php:234
‪TYPO3\CMS\Linkvalidator\Tests\Unit\Linktype\ExternalLinktypeTest\preprocessUrlReturnsCorrectString
‪preprocessUrlReturnsCorrectString(string $inputUrl, $expectedResult)
Definition: ExternalLinktypeTest.php:200
‪TYPO3\CMS\Linkvalidator\Tests\Unit\Linktype\ExternalLinktypeTest\setUp
‪setUp()
Definition: ExternalLinktypeTest.php:36
‪TYPO3\CMS\Linkvalidator\Tests\Unit\Linktype\ExternalLinktypeTest
Definition: ExternalLinktypeTest.php:34
‪TYPO3\CMS\Linkvalidator\Tests\Unit\Linktype
Definition: ExternalLinktypeTest.php:18
‪TYPO3\CMS\Linkvalidator\Linktype\ExternalLinktype
Definition: ExternalLinktype.php:32
‪TYPO3\CMS\Core\Http\RequestFactory
Definition: RequestFactory.php:31
‪TYPO3\CMS\Linkvalidator\Tests\Unit\Linktype\ExternalLinktypeTest\setAdditionalConfigAppendsEmailFromGlobalsIfConfigured
‪setAdditionalConfigAppendsEmailFromGlobalsIfConfigured()
Definition: ExternalLinktypeTest.php:311
‪TYPO3\CMS\Linkvalidator\Tests\Unit\Linktype\ExternalLinktypeTest\setAdditionalConfigAppendsEmailIfConfigured
‪setAdditionalConfigAppendsEmailIfConfigured()
Definition: ExternalLinktypeTest.php:291
‪TYPO3\CMS\Linkvalidator\Tests\Unit\Linktype\ExternalLinktypeTest\setAdditionalConfigAppendsAgentUrlIfConfigured
‪setAdditionalConfigAppendsAgentUrlIfConfigured()
Definition: ExternalLinktypeTest.php:271
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Linkvalidator\Tests\Unit\Linktype\ExternalLinktypeTest\buildLanguageServiceProphecy
‪buildLanguageServiceProphecy()
Definition: ExternalLinktypeTest.php:42
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:42
‪TYPO3\CMS\Linkvalidator\Tests\Unit\Linktype\ExternalLinktypeTest\getRequestHeaderOptions
‪getRequestHeaderOptions()
Definition: ExternalLinktypeTest.php:121
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:50
‪TYPO3\CMS\Linkvalidator\Tests\Unit\Linktype\ExternalLinktypeTest\setAdditionalConfigSetsRangeAndMethod
‪setAdditionalConfigSetsRangeAndMethod()
Definition: ExternalLinktypeTest.php:330
‪TYPO3\CMS\Linkvalidator\Tests\Unit\Linktype\ExternalLinktypeTest\checkLinkWithExternalUrlNotFoundReturnsFalse
‪checkLinkWithExternalUrlNotFoundReturnsFalse()
Definition: ExternalLinktypeTest.php:54
‪TYPO3\CMS\Linkvalidator\Tests\Unit\Linktype\ExternalLinktypeTest\getCookieJarProphecy
‪getCookieJarProphecy()
Definition: ExternalLinktypeTest.php:113