diff --git a/src/Symfony/Component/BrowserKit/Client.php b/src/Symfony/Component/BrowserKit/Client.php index 799b3579f0f69..ddd99a1ce2622 100644 --- a/src/Symfony/Component/BrowserKit/Client.php +++ b/src/Symfony/Component/BrowserKit/Client.php @@ -40,6 +40,7 @@ abstract class Client protected $insulated = false; protected $redirect; protected $followRedirects = true; + protected $followMetaRefresh = false; private $maxRedirects = -1; private $redirectCount = 0; @@ -68,6 +69,14 @@ public function followRedirects($followRedirect = true) $this->followRedirects = (bool) $followRedirect; } + /** + * Sets whether to automatically follow meta refresh redirects or not. + */ + public function followMetaRefresh(bool $followMetaRefresh = true) + { + $this->followMetaRefresh = $followMetaRefresh; + } + /** * Returns whether client automatically follows redirects or not. * @@ -367,7 +376,16 @@ public function request(string $method, string $uri, array $parameters = array() return $this->crawler = $this->followRedirect(); } - return $this->crawler = $this->createCrawlerFromContent($this->internalRequest->getUri(), $this->internalResponse->getContent(), $this->internalResponse->getHeader('Content-Type')); + $this->crawler = $this->createCrawlerFromContent($this->internalRequest->getUri(), $this->internalResponse->getContent(), $this->internalResponse->getHeader('Content-Type')); + + // Check for meta refresh redirect + if ($this->followMetaRefresh && null !== $redirect = $this->getMetaRefreshUrl()) { + $this->redirect = $redirect; + $this->redirects[serialize($this->history->current())] = true; + $this->crawler = $this->followRedirect(); + } + + return $this->crawler; } /** @@ -563,6 +581,21 @@ public function followRedirect() return $response; } + /** + * @see https://dev.w3.org/html5/spec-preview/the-meta-element.html#attr-meta-http-equiv-refresh + */ + private function getMetaRefreshUrl(): ?string + { + $metaRefresh = $this->getCrawler()->filter('head meta[http-equiv="refresh"]'); + foreach ($metaRefresh->extract(array('content')) as $content) { + if (preg_match('/^\s*0\s*;\s*URL\s*=\s*(?|\'([^\']++)|"([^"]++)|([^\'"].*))/i', $content, $m)) { + return str_replace("\t\r\n", '', rtrim($m[1])); + } + } + + return null; + } + /** * Restarts the client. * diff --git a/src/Symfony/Component/BrowserKit/Tests/ClientTest.php b/src/Symfony/Component/BrowserKit/Tests/ClientTest.php index 909bf3c67c245..020fa07526e03 100644 --- a/src/Symfony/Component/BrowserKit/Tests/ClientTest.php +++ b/src/Symfony/Component/BrowserKit/Tests/ClientTest.php @@ -594,6 +594,39 @@ public function testFollowRedirectDropPostMethod() } } + /** + * @dataProvider getTestsForMetaRefresh + */ + public function testFollowMetaRefresh(string $content, string $expectedEndingUrl, bool $followMetaRefresh = true) + { + $client = new TestClient(); + $client->followMetaRefresh($followMetaRefresh); + $client->setNextResponse(new Response($content)); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $this->assertEquals($expectedEndingUrl, $client->getRequest()->getUri()); + } + + public function getTestsForMetaRefresh() + { + return array( + array('', 'http://www.example.com/redirected'), + array('', 'http://www.example.com/redirected'), + array('', 'http://www.example.com/redirected'), + array('', 'http://www.example.com/redirected'), + array('', 'http://www.example.com/redirected'), + array('', 'http://www.example.com/redirected'), + array('', 'http://www.example.com/redirected'), + array('', 'http://www.example.com/redirected'), + // Non-zero timeout should not result in a redirect. + array('', 'http://www.example.com/foo/foobar'), + array('', 'http://www.example.com/foo/foobar'), + // Invalid meta tag placement should not result in a redirect. + array('', 'http://www.example.com/foo/foobar'), + // Valid meta refresh should not be followed if disabled. + array('', 'http://www.example.com/foo/foobar', false), + ); + } + public function testBack() { $client = new TestClient();