diff --git a/typo3/sysext/core/Classes/Utility/GeneralUtility.php b/typo3/sysext/core/Classes/Utility/GeneralUtility.php index d2d11562be053363aca3d43d57fad7746781e153..769dca7c764dd6a8fd812ba4d361d40d40f18b74 100644 --- a/typo3/sysext/core/Classes/Utility/GeneralUtility.php +++ b/typo3/sysext/core/Classes/Utility/GeneralUtility.php @@ -2418,14 +2418,17 @@ class GeneralUtility { } return FALSE; } + + $followLocationSucceeded = @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_HEADER, $includeHeader ? 1 : 0); + curl_setopt($ch, CURLOPT_HEADER, !$followLocationSucceeded || $includeHeader ? 1 : 0); curl_setopt($ch, CURLOPT_NOBODY, $includeHeader == 2 ? 1 : 0); curl_setopt($ch, CURLOPT_HTTPGET, $includeHeader == 2 ? 'HEAD' : 'GET'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_FAILONERROR, 1); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, max(0, (int)$GLOBALS['TYPO3_CONF_VARS']['SYS']['curlTimeout'])); - $followLocation = @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); + if (is_array($requestHeaders)) { curl_setopt($ch, CURLOPT_HTTPHEADER, $requestHeaders); } @@ -2443,21 +2446,42 @@ class GeneralUtility { } } $content = curl_exec($ch); + $curlInfo = curl_getinfo($ch); + + if (!$followLocationSucceeded) { + // Check if we need to do redirects + if ($curlInfo['http_code'] >= 300 && $curlInfo['http_code'] < 400) { + $locationUrl = $curlInfo['redirect_url']; + if (!$locationUrl) { + // Some curllib versions do not return redirect_url. Examine headers. + $locationUrl = self::getRedirectUrlFromHttpHeaders($content); + } + if ($locationUrl) { + $content = self::getUrl($locationUrl, $includeHeader, $requestHeaders, $report); + $followLocationSucceeded = TRUE; + } else { + // Failure: we got a redirection status code but not the URL to redirect to. + $content = FALSE; + } + } + if ($content && !$includeHeader) { + $content = self::stripHttpHeaders($content); + } + } + if (isset($report)) { - if ($content === FALSE) { + if (!$followLocationSucceeded && $curlInfo['http_code'] >= 300 && $curlInfo['http_code'] < 400) { + $report['http_code'] = $curlInfo['http_code']; + $report['content_type'] = $curlInfo['content_type']; + $report['error'] = CURLE_GOT_NOTHING; + $report['message'] = 'Expected "Location" header but got nothing.'; + } elseif ($content === FALSE) { $report['error'] = curl_errno($ch); $report['message'] = curl_error($ch); - } else { - $curlInfo = curl_getinfo($ch); - // We hit a redirection but we couldn't follow it - if (!$followLocation && $curlInfo['status'] >= 300 && $curlInfo['status'] < 400) { - $report['error'] = -1; - $report['message'] = 'Couldn\'t follow location redirect (PHP configuration option open_basedir is in effect).'; - } elseif ($includeHeader) { - // Set only for $includeHeader to work exactly like PHP variant - $report['http_code'] = $curlInfo['http_code']; - $report['content_type'] = $curlInfo['content_type']; - } + } elseif ($includeHeader) { + // Set only for $includeHeader to work exactly like PHP variant + $report['http_code'] = $curlInfo['http_code']; + $report['content_type'] = $curlInfo['content_type']; } } curl_close($ch); @@ -2557,6 +2581,45 @@ Connection: close return $content; } + /** + * Parses HTTP headers and returns the content of the "Location" header + * or the empty string if no such header found. + * + * @param string $content + * @return string + */ + static protected function getRedirectUrlFromHttpHeaders($content) { + $result = ''; + $headers = explode("\r\n", $content); + foreach ($headers as $header) { + if ($header == '') { + break; + } + if (preg_match('/^\s*Location\s*:/i', $header)) { + list(, $result) = self::trimExplode(':', $header, FALSE, 2); + if ($result) { + $result = self::locationHeaderUrl($result); + } + break; + } + } + return $result; + } + + /** + * Strips HTTP headers from the content. + * + * @param string $content + * @return string + */ + static protected function stripHttpHeaders($content) { + $headersEndPos = strpos($content, "\r\n\r\n"); + if ($headersEndPos) { + $content = substr($content, $headersEndPos + 4); + } + return $content; + } + /** * Writes $content to the file $file * diff --git a/typo3/sysext/core/Tests/Unit/Utility/Fixtures/GeneralUtilityFixture.php b/typo3/sysext/core/Tests/Unit/Utility/Fixtures/GeneralUtilityFixture.php index 4e99ec14af9d19ec4ef3a6edd909582f83f992ed..97c1a41ca6512d7ab285d875b3c5ff02d30581c6 100644 --- a/typo3/sysext/core/Tests/Unit/Utility/Fixtures/GeneralUtilityFixture.php +++ b/typo3/sysext/core/Tests/Unit/Utility/Fixtures/GeneralUtilityFixture.php @@ -53,5 +53,24 @@ class GeneralUtilityFixture extends GeneralUtility { return FALSE; } + /** + * Parses HTTP headers and returns the content of the "Location" header + * or the empty string if no such header found. + * + * @param string $content + * @return string + */ + static public function getRedirectUrlFromHttpHeaders($content) { + return parent::getRedirectUrlFromHttpHeaders($content); + } + /** + * Strips HTTP headers from the content. + * + * @param string $content + * @return string + */ + static public function stripHttpHeaders($content) { + return parent::stripHttpHeaders($content); + } } \ No newline at end of file diff --git a/typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php b/typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php index bc2456d329c02c671efcf8165578c765a98acb3a..190d992d59e82709713b5b62e355f35da4d1268a 100644 --- a/typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php +++ b/typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php @@ -4511,9 +4511,48 @@ text with a ' . $urlMatch . '$|s'), * @param string $input Text to recognise URLs from * @param string $expected Text with correctly detected URLs */ - public function substUrlsInPlainText($input, $expectedPreg) { + public function substUrlsInPlainText($input, $expected) { $GLOBALS['TYPO3_DB'] = $this->getMock('TYPO3\\CMS\\Core\\Database\\DatabaseConnection', array(), array(), '', FALSE); - $this->assertTrue(preg_match($expectedPreg, Utility\GeneralUtility::substUrlsInPlainText($input, 1, 'http://example.com/index.php')) == 1); + $this->assertTrue(preg_match($expected, Utility\GeneralUtility::substUrlsInPlainText($input, 1, 'http://example.com/index.php')) == 1); } + /** + * @return array + */ + public function getRedirectUrlFromHttpHeadersDataProvider() { + return array( + 'Extracts redirect URL from Location header' => array("HTTP/1.0 302 Redirect\r\nServer: Apache\r\nLocation: http://example.com/\r\nX-pad: avoid browser bug\r\n\r\nLocation: test\r\n", 'http://example.com/'), + 'Returns empty string if no Location is found in header' => array("HTTP/1.0 302 Redirect\r\nServer: Apache\r\nX-pad: avoid browser bug\r\n\r\nLocation: test\r\n", ''), + ); + } + + /** + * @param string $httpResponse + * @param string $expected + * @test + * @dataProvider getRedirectUrlFromHttpHeadersDataProvider + */ + public function getRedirectUrlReturnsRedirectUrlFromHttpResponse($httpResponse, $expected) { + $this->assertEquals($expected, GeneralUtilityFixture::getRedirectUrlFromHttpHeaders($httpResponse)); + } + + /** + * @return array + */ + public function getStripHttpHeadersDataProvider() { + return array( + 'Simple content' => array("HTTP/1.0 302 Redirect\r\nServer: Apache\r\nX-pad: avoid browser bug\r\n\r\nHello, world!", 'Hello, world!'), + 'Content with multiple returns' => array("HTTP/1.0 302 Redirect\r\nServer: Apache\r\nX-pad: avoid browser bug\r\n\r\nHello, world!\r\n\r\nAnother hello here!", "Hello, world!\r\n\r\nAnother hello here!"), + ); + } + + /** + * @param string $httpResponse + * @param string $expected + * @test + * @dataProvider getStripHttpHeadersDataProvider + */ + public function stripHttpHeadersStripsHeadersFromHttpResponse($httpResponse, $expected) { + $this->assertEquals($expected, GeneralUtilityFixture::stripHttpHeaders($httpResponse)); + } }