[BUGFIX] Restore getUrl support for list of headers 46/56046/7
authorFelix Buenemann <felix.buenemann@gmail.com>
Wed, 7 Mar 2018 22:39:24 +0000 (23:39 +0100)
committerSusanne Moog <susanne.moog@typo3.org>
Wed, 14 Mar 2018 16:44:17 +0000 (17:44 +0100)
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>
typo3/sysext/core/Classes/Utility/GeneralUtility.php
typo3/sysext/core/Documentation/Changelog/master/Deprecation-84171-AddingGeneralUtilitygetUrlRequestHeadersAsNon-associativeArrayAreDeprecated.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php
typo3/sysext/core/Tests/UnitDeprecated/Utility/GeneralUtilityTest.php [new file with mode: 0644]
typo3/sysext/frontend/Classes/Controller/ErrorController.php

index 44725e4..195d267 100644 (file)
@@ -1799,6 +1799,12 @@ class GeneralUtility
             /** @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 = [];
@@ -1871,6 +1877,38 @@ class GeneralUtility
     }
 
     /**
+     * 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
      *
      * @param string $file Filepath to write to
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-84171-AddingGeneralUtilitygetUrlRequestHeadersAsNon-associativeArrayAreDeprecated.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-84171-AddingGeneralUtilitygetUrlRequestHeadersAsNon-associativeArrayAreDeprecated.rst
new file mode 100644 (file)
index 0000000..3c29731
--- /dev/null
@@ -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
\ No newline at end of file
index b5f3587..50f1168 100644 (file)
@@ -17,7 +17,11 @@ namespace TYPO3\CMS\Core\Tests\Unit\Utility;
 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;
@@ -4786,4 +4790,34 @@ class GeneralUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
             ]
         ];
     }
+
+    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();
+    }
 }
diff --git a/typo3/sysext/core/Tests/UnitDeprecated/Utility/GeneralUtilityTest.php b/typo3/sysext/core/Tests/UnitDeprecated/Utility/GeneralUtilityTest.php
new file mode 100644 (file)
index 0000000..e8d847a
--- /dev/null
@@ -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();
+    }
+}
index 6d0f1b5..c6c816a 100644 (file)
@@ -182,8 +182,8 @@ class ErrorController
             }
             // 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);