Skip to content

Commit

Permalink
[BUGFIX] Restore getUrl support for list of headers
Browse files Browse the repository at this point in the history
The change of \TYPO3\CMS\Core\Utility\GeneralUtility::getUrl()
from cURL to GuzzleHttp the format of the $requestHeaders param was
implicitly changed from an array of header strings to an associative
array where the key is the header name and the value is either a single
or an array of values for that header.

This adds back support for the old list of headers format by detecting a
non-associative array and converting it to the Guzzle key/value(s) style.

At the same time the 'old' way is deprecated.

Resolves: #84171
Related: #70056
Releases: master, 8.7
Change-Id: I41b23993957288dfd5294129fa8039aab717461d
Reviewed-on: https://review.typo3.org/56046
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Frank Naegler <frank.naegler@typo3.org>
Tested-by: Frank Naegler <frank.naegler@typo3.org>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
  • Loading branch information
felixbuenemann authored and susannemoog committed Mar 14, 2018
1 parent d8e0000 commit 662fb9a
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 2 deletions.
38 changes: 38 additions & 0 deletions typo3/sysext/core/Classes/Utility/GeneralUtility.php
Expand Up @@ -1799,6 +1799,12 @@ public static function getUrl($url, $includeHeader = 0, $requestHeaders = null,
/** @var RequestFactory $requestFactory */
$requestFactory = static::makeInstance(RequestFactory::class);
if (is_array($requestHeaders)) {
// Check is $requestHeaders is an associative array or not
if (count(array_filter(array_keys($requestHeaders), 'is_string')) === 0) {
trigger_error('Request headers as colon-separated string are deprecated, use an associative array instead.', E_USER_DEPRECATED);
// Convert cURL style lines of headers to Guzzle key/value(s) pairs.
$requestHeaders = static::splitHeaderLines($requestHeaders);
}
$configuration = ['headers' => $requestHeaders];
} else {
$configuration = [];
Expand Down Expand Up @@ -1870,6 +1876,38 @@ public static function getUrl($url, $includeHeader = 0, $requestHeaders = null,
return $content;
}

/**
* Split an array of MIME header strings into an associative array.
* Multiple headers with the same name have their values merged as an array.
*
* @static
* @param array $headers List of headers, eg. ['Foo: Bar', 'Foo: Baz']
* @return array Key/Value(s) pairs of headers, eg. ['Foo' => ['Bar', 'Baz']]
*/
protected static function splitHeaderLines(array $headers): array
{
$newHeaders = [];
foreach ($headers as $header) {
$parts = preg_split('/:[ \t]*/', $header, 2, PREG_SPLIT_NO_EMPTY);
if (count($parts) !== 2) {
continue;
}
$key = &$parts[0];
$value = &$parts[1];
if (array_key_exists($key, $newHeaders)) {
if (is_array($newHeaders[$key])) {
$newHeaders[$key][] = $value;
} else {
$prevValue = &$newHeaders[$key];
$newHeaders[$key] = [$prevValue, $value];
}
} else {
$newHeaders[$key] = $value;
}
}
return $newHeaders;
}

/**
* Writes $content to the file $file
*
Expand Down
@@ -0,0 +1,42 @@
.. include:: ../../Includes.txt

==========================================================================================================
Deprecation: #84171 - Adding GeneralUtility::getUrl RequestHeaders as non-associative array are deprecated
==========================================================================================================

See :issue:`84171`

Description
===========

RequestHeaders passed to getUrl as string (format `Header:Value`) have been deprecated. Associative arrays should be used instead.


Impact
======

Using `GeneralUtility::getUrl` request headers in a non-associative way will trigger an `E_USER_DEPRECATED` PHP error.


Affected Installations
======================

All using request headers for `GeneralUtility::getUrl` in a non-associative way.


Migration
=========

Use associative arrays, for example:

.. code-block:: php
$headers = ['Content-Language: de-DE'];
will become

.. code-block:: php
$headers = ['Content-Language' => 'de-DE'];
.. index:: PHP-API, NotScanned
34 changes: 34 additions & 0 deletions typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php
Expand Up @@ -17,7 +17,11 @@
use org\bovigo\vfs\vfsStream;
use org\bovigo\vfs\vfsStreamDirectory;
use org\bovigo\vfs\vfsStreamWrapper;
use Prophecy\Argument;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Log\LoggerInterface;
use TYPO3\CMS\Core\Http\RequestFactory;
use TYPO3\CMS\Core\Package\Package;
use TYPO3\CMS\Core\Package\PackageManager;
use TYPO3\CMS\Core\Tests\Unit\Utility\AccessibleProxies\ExtensionManagementUtilityAccessibleProxy;
Expand Down Expand Up @@ -4786,4 +4790,34 @@ public function idnaEncodeDataProvider()
]
];
}

public function splitHeaderLinesDataProvider(): array
{
return [
'multi-line headers' => [
['Content-Type' => 'multipart/form-data; boundary=something', 'Content-Language' => 'de-DE, en-CA'],
['Content-Type' => 'multipart/form-data; boundary=something', 'Content-Language' => 'de-DE, en-CA'],
]
];
}

/**
* @test
* @dataProvider splitHeaderLinesDataProvider
* @param array $headers
* @param array $expectedHeaders
*/
public function splitHeaderLines(array $headers, array $expectedHeaders): void
{
$stream = $this->prophesize(StreamInterface::class);
$response = $this->prophesize(ResponseInterface::class);
$response->getBody()->willReturn($stream);
$requestFactory = $this->prophesize(RequestFactory::class);
$requestFactory->request(Argument::cetera())->willReturn($response);

GeneralUtility::addInstance(RequestFactory::class, $requestFactory->reveal());
GeneralUtility::getUrl('http://example.com', 0, $headers);

$requestFactory->request(Argument::any(), Argument::any(), ['headers' => $expectedHeaders])->shouldHaveBeenCalled();
}
}
@@ -0,0 +1,70 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Core\Tests\UnitDeprecated\Utility;

/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

use Prophecy\Argument;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use TYPO3\CMS\Core\Http\RequestFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;

/**
* Testcase for the \TYPO3\CMS\Core\Utility\ClientUtility class.
*/
class GeneralUtilityTest extends UnitTestCase
{
public function splitHeaderLinesDataProvider(): array
{
return [
'one-line, single header' => [
['Content-Security-Policy:default-src \'self\'; img-src https://*; child-src \'none\';'],
['Content-Security-Policy' => 'default-src \'self\'; img-src https://*; child-src \'none\';']
],
'one-line, multiple headers' => [
[
'Content-Security-Policy:default-src \'self\'; img-src https://*; child-src \'none\';',
'Content-Security-Policy-Report-Only:default-src https:; report-uri /csp-violation-report-endpoint/'
],
[
'Content-Security-Policy' => 'default-src \'self\'; img-src https://*; child-src \'none\';',
'Content-Security-Policy-Report-Only' => 'default-src https:; report-uri /csp-violation-report-endpoint/'
]
]
];
}

/**
* @test
* @dataProvider splitHeaderLinesDataProvider
* @param array $headers
* @param array $expectedHeaders
*/
public function splitHeaderLines(array $headers, array $expectedHeaders): void
{
$stream = $this->prophesize(StreamInterface::class);
$response = $this->prophesize(ResponseInterface::class);
$response->getBody()->willReturn($stream);
$requestFactory = $this->prophesize(RequestFactory::class);
$requestFactory->request(Argument::cetera())->willReturn($response);

GeneralUtility::addInstance(RequestFactory::class, $requestFactory->reveal());
GeneralUtility::getUrl('http://example.com', 0, $headers);

$requestFactory->request(Argument::any(), Argument::any(), ['headers' => $expectedHeaders])
->shouldHaveBeenCalled();
}
}
4 changes: 2 additions & 2 deletions typo3/sysext/frontend/Classes/Controller/ErrorController.php
Expand Up @@ -182,8 +182,8 @@ protected function handlePageError($errorHandler, string $header = '', string $r
}
// Prepare headers
$requestHeaders = [
'User-agent: ' . GeneralUtility::getIndpEnv('HTTP_USER_AGENT'),
'Referer: ' . GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL')
'User-agent' => GeneralUtility::getIndpEnv('HTTP_USER_AGENT'),
'Referer' => GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL')
];
$report = [];
$res = GeneralUtility::getUrl($errorHandler, 1, $requestHeaders, $report);
Expand Down

0 comments on commit 662fb9a

Please sign in to comment.