[!!!][FEATURE] Introduce Guzzle for making HTTP Requests 08/43508/22
authorBenni Mack <benni@typo3.org>
Wed, 30 Mar 2016 11:58:11 +0000 (13:58 +0200)
committerSusanne Moog <typo3@susannemoog.de>
Thu, 31 Mar 2016 21:57:09 +0000 (23:57 +0200)
The Guzzle library is added as a composer dependency
instead of HttpRequest to request urls from TYPO3.

Guzzle is encapsulated inside a RequestFactory to do
requests and return PSR-7 compliant Response objects
to deal with content. In the future, Guzzle can also be used
to do HTTP requests asynchronously but there is no API
for that yet as there is no current need for that in the
TYPO3 Core.

GeneralUtility::getUrl() now uses Guzzle under the hood,
thus adding headers like the TYPO3 User Agent by default.

A lot of existing TYPO3_CONF_VARS options are now
removed or merged into Guzzle compliant options which
are independant of the implementation (cURL, stream
wrappers, fopen etc).

Resolves: #70056
Releases: master
Change-Id: Ibd14bba944b1590bae1b12c10f26365f20576475
Reviewed-on: https://review.typo3.org/43508
Reviewed-by: Morton Jonuschat <m.jonuschat@mojocode.de>
Tested-by: Morton Jonuschat <m.jonuschat@mojocode.de>
Reviewed-by: Markus Klein <markus.klein@typo3.org>
Tested-by: Markus Klein <markus.klein@typo3.org>
Reviewed-by: Susanne Moog <typo3@susannemoog.de>
Tested-by: Susanne Moog <typo3@susannemoog.de>
22 files changed:
composer.json
composer.lock
typo3/sysext/backend/Classes/Clipboard/Clipboard.php
typo3/sysext/core/Classes/Core/Bootstrap.php
typo3/sysext/core/Classes/Http/HttpRequest.php [deleted file]
typo3/sysext/core/Classes/Http/Observer/Download.php [deleted file]
typo3/sysext/core/Classes/Http/RequestFactory.php [new file with mode: 0644]
typo3/sysext/core/Classes/Utility/GeneralUtility.php
typo3/sysext/core/Configuration/DefaultConfiguration.php
typo3/sysext/core/Documentation/Changelog/master/Breaking-70056-CurlAndHttpRequestRemoved.rst [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Feature-70056-GuzzleForHttpRequests.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Http/HttpRequestTest.php [deleted file]
typo3/sysext/core/Tests/Unit/Utility/Fixtures/GeneralUtilityFixture.php
typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php
typo3/sysext/documentation/Classes/Service/DocumentationService.php
typo3/sysext/extensionmanager/Classes/Utility/Connection/TerUtility.php
typo3/sysext/extensionmanager/Classes/Utility/Repository/Helper.php
typo3/sysext/install/Classes/Service/SilentConfigurationUpgradeService.php
typo3/sysext/install/Classes/Updates/AbstractDownloadExtensionUpdate.php
typo3/sysext/install/Tests/Unit/Service/SilentConfigurationUpgradeServiceTest.php
typo3/sysext/lang/Classes/Service/TerService.php
typo3/sysext/linkvalidator/Classes/Linktype/ExternalLinktype.php

index db8864c..358910a 100644 (file)
@@ -38,7 +38,6 @@
                "ext-session": "*",
                "ext-xml": "*",
                "psr/log": "1.0.0",
-               "pear/http_request2": "~2.2.1",
                "swiftmailer/swiftmailer": "~5.4.1",
                "symfony/console": ">=2.7|<3.1",
                "symfony/finder": ">=2.7|<3.1",
@@ -48,7 +47,8 @@
                "psr/http-message": "~1.0",
                "cogpowered/finediff": "~0.3.1",
                "mso/idna-convert": "^0.9.1",
-               "typo3fluid/fluid": "^1.0.6"
+               "typo3fluid/fluid": "^1.0.6",
+               "guzzlehttp/guzzle": "^6.2.0"
        },
        "require-dev": {
                "phpunit/phpunit": "~5.2.0",
                        "TYPO3\\CMS\\Recycler\\Tests\\": "typo3/sysext/recycler/Tests/"
                },
                "classmap": ["typo3/sysext/extbase/Tests/Unit/Object/Container/Fixtures/"]
-       },
-       "include-path": [
-               "vendor/pear/http_request2/",
-               "vendor/pear/net_url2/"
-       ]
+       }
 }
index ab07fd8..7a663b4 100644 (file)
@@ -4,8 +4,8 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "hash": "82c74b3e141085ced0e704ebb8ea114b",
-    "content-hash": "c30d9ff58c2c7c805bd2e1fce8c0b08b",
+    "hash": "e346427bb60ae5bf1fb7c5b6128dd888",
+    "content-hash": "d9d04a98027086cd867bb7dbff748e7a",
     "packages": [
         {
             "name": "cogpowered/finediff",
             "time": "2015-06-14 21:17:01"
         },
         {
-            "name": "mso/idna-convert",
-            "version": "v0.9.1",
+            "name": "guzzlehttp/guzzle",
+            "version": "6.2.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/phlylabs/idna-convert.git",
-                "reference": "0a0a09e460e63739d82c3f0c3f0e5555567a6be3"
+                "url": "https://github.com/guzzle/guzzle.git",
+                "reference": "d094e337976dff9d8e2424e8485872194e768662"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phlylabs/idna-convert/zipball/0a0a09e460e63739d82c3f0c3f0e5555567a6be3",
-                "reference": "0a0a09e460e63739d82c3f0c3f0e5555567a6be3",
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d094e337976dff9d8e2424e8485872194e768662",
+                "reference": "d094e337976dff9d8e2424e8485872194e768662",
                 "shasum": ""
             },
             "require": {
-                "ext-pcre": "*",
-                "php": ">=5.0.0"
+                "guzzlehttp/promises": "~1.0",
+                "guzzlehttp/psr7": "~1.1",
+                "php": ">=5.5.0"
+            },
+            "require-dev": {
+                "ext-curl": "*",
+                "phpunit/phpunit": "~4.0",
+                "psr/log": "~1.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "0.9.x-dev"
+                    "dev-master": "6.2-dev"
                 }
             },
             "autoload": {
-                "classmap": [
-                    "idna_convert.class.php",
-                    "uctc.php"
-                ]
+                "files": [
+                    "src/functions_include.php"
+                ],
+                "psr-4": {
+                    "GuzzleHttp\\": "src/"
+                }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "LGPL-2.1+"
+                "MIT"
             ],
             "authors": [
                 {
-                    "name": "Matthias Sommerfeld",
-                    "email": "mso@phlylabs.de",
-                    "role": "Developer"
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
                 }
             ],
-            "description": "A library for encoding and decoding internationalized domain names",
-            "homepage": "http://idnaconv.net/",
+            "description": "Guzzle is a PHP HTTP client library",
+            "homepage": "http://guzzlephp.org/",
             "keywords": [
-                "idn",
-                "idna",
-                "php"
+                "client",
+                "curl",
+                "framework",
+                "http",
+                "http client",
+                "rest",
+                "web service"
             ],
-            "time": "2016-01-06 21:05:46"
+            "time": "2016-03-21 20:02:09"
         },
         {
-            "name": "pear/http_request2",
-            "version": "v2.2.1",
+            "name": "guzzlehttp/promises",
+            "version": "1.1.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/pear/HTTP_Request2.git",
-                "reference": "d6c81670c504045248c1afdf896bb9a3288158de"
+                "url": "https://github.com/guzzle/promises.git",
+                "reference": "bb9024c526b22f3fe6ae55a561fd70653d470aa8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/pear/HTTP_Request2/zipball/d6c81670c504045248c1afdf896bb9a3288158de",
-                "reference": "d6c81670c504045248c1afdf896bb9a3288158de",
+                "url": "https://api.github.com/repos/guzzle/promises/zipball/bb9024c526b22f3fe6ae55a561fd70653d470aa8",
+                "reference": "bb9024c526b22f3fe6ae55a561fd70653d470aa8",
                 "shasum": ""
             },
             "require": {
-                "pear/net_url2": ">=2.0.0",
-                "pear/pear_exception": "*",
-                "php": ">=5.2.0"
+                "php": ">=5.5.0"
             },
-            "suggest": {
-                "ext-fileinfo": "Adds support for looking up mime-types using finfo.",
-                "ext-zlib": "Allows handling gzip compressed responses.",
-                "lib-curl": "Allows using cURL as a request backend.",
-                "lib-openssl": "Allows handling SSL requests when not using cURL."
+            "require-dev": {
+                "phpunit/phpunit": "~4.0"
             },
             "type": "library",
-            "autoload": {
-                "psr-0": {
-                    "HTTP_Request2": ""
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
                 }
             },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Promise\\": "src/"
+                },
+                "files": [
+                    "src/functions_include.php"
+                ]
+            },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "BSD-3-Clause"
+                "MIT"
             ],
             "authors": [
                 {
-                    "name": "Alexey Borzov",
-                    "email": "avb@php.net",
-                    "role": "Developer HTML_Common2"
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
                 }
             ],
-            "description": "Provides an easy way to perform HTTP requests.",
-            "homepage": "http://pear.php.net/package/HTTP_Request2",
+            "description": "Guzzle promises library",
             "keywords": [
-                "PEAR",
-                "curl",
-                "http",
-                "request"
+                "promise"
             ],
-            "time": "2014-01-16 17:27:21"
+            "time": "2016-03-08 01:15:46"
         },
         {
-            "name": "pear/net_url2",
-            "version": "v2.2.0",
+            "name": "guzzlehttp/psr7",
+            "version": "1.2.3",
             "source": {
                 "type": "git",
-                "url": "https://github.com/pear/Net_URL2.git",
-                "reference": "fa9b1ecb3c3e640d4a54d58d681a4cb7524f209e"
+                "url": "https://github.com/guzzle/psr7.git",
+                "reference": "2e89629ff057ebb49492ba08e6995d3a6a80021b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/pear/Net_URL2/zipball/fa9b1ecb3c3e640d4a54d58d681a4cb7524f209e",
-                "reference": "fa9b1ecb3c3e640d4a54d58d681a4cb7524f209e",
+                "url": "https://api.github.com/repos/guzzle/psr7/zipball/2e89629ff057ebb49492ba08e6995d3a6a80021b",
+                "reference": "2e89629ff057ebb49492ba08e6995d3a6a80021b",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.1.4"
+                "php": ">=5.4.0",
+                "psr/http-message": "~1.0"
+            },
+            "provide": {
+                "psr/http-message-implementation": "1.0"
             },
             "require-dev": {
-                "phpunit/phpunit": ">=3.3.0"
+                "phpunit/phpunit": "~4.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.2.x-dev"
+                    "dev-master": "1.0-dev"
                 }
             },
             "autoload": {
-                "classmap": [
-                    "Net/URL2.php"
+                "psr-4": {
+                    "GuzzleHttp\\Psr7\\": "src/"
+                },
+                "files": [
+                    "src/functions_include.php"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "BSD-3-Clause"
+                "MIT"
             ],
             "authors": [
                 {
-                    "name": "David Coallier",
-                    "email": "davidc@php.net"
-                },
-                {
-                    "name": "Tom Klingenberg",
-                    "email": "tkli@php.net"
-                },
-                {
-                    "name": "Christian Schmidt",
-                    "email": "chmidt@php.net"
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
                 }
             ],
-            "description": "Class for parsing and handling URL. Provides parsing of URLs into their constituent parts (scheme, host, path etc.), URL generation, and resolving of relative URLs.",
-            "homepage": "https://github.com/pear/Net_URL2",
+            "description": "PSR-7 message implementation",
             "keywords": [
-                "PEAR",
-                "net",
-                "networking",
-                "rfc3986",
-                "uri",
-                "url"
-            ],
-            "time": "2015-04-18 17:36:57"
+                "http",
+                "message",
+                "stream",
+                "uri"
+            ],
+            "time": "2016-02-18 21:54:00"
         },
         {
-            "name": "pear/pear_exception",
-            "version": "v1.0.0",
+            "name": "mso/idna-convert",
+            "version": "v0.9.1",
             "source": {
                 "type": "git",
-                "url": "https://github.com/pear/PEAR_Exception.git",
-                "reference": "8c18719fdae000b690e3912be401c76e406dd13b"
+                "url": "https://github.com/phlylabs/idna-convert.git",
+                "reference": "0a0a09e460e63739d82c3f0c3f0e5555567a6be3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/pear/PEAR_Exception/zipball/8c18719fdae000b690e3912be401c76e406dd13b",
-                "reference": "8c18719fdae000b690e3912be401c76e406dd13b",
+                "url": "https://api.github.com/repos/phlylabs/idna-convert/zipball/0a0a09e460e63739d82c3f0c3f0e5555567a6be3",
+                "reference": "0a0a09e460e63739d82c3f0c3f0e5555567a6be3",
                 "shasum": ""
             },
             "require": {
-                "php": ">=4.4.0"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "*"
+                "ext-pcre": "*",
+                "php": ">=5.0.0"
             },
-            "type": "class",
+            "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.0.x-dev"
+                    "dev-master": "0.9.x-dev"
                 }
             },
             "autoload": {
-                "psr-0": {
-                    "PEAR": ""
-                }
+                "classmap": [
+                    "idna_convert.class.php",
+                    "uctc.php"
+                ]
             },
             "notification-url": "https://packagist.org/downloads/",
-            "include-path": [
-                "."
-            ],
             "license": [
-                "BSD-2-Clause"
+                "LGPL-2.1+"
             ],
             "authors": [
                 {
-                    "name": "Helgi Thormar",
-                    "email": "dufuz@php.net"
-                },
-                {
-                    "name": "Greg Beaver",
-                    "email": "cellog@php.net"
+                    "name": "Matthias Sommerfeld",
+                    "email": "mso@phlylabs.de",
+                    "role": "Developer"
                 }
             ],
-            "description": "The PEAR Exception base class.",
-            "homepage": "https://github.com/pear/PEAR_Exception",
+            "description": "A library for encoding and decoding internationalized domain names",
+            "homepage": "http://idnaconv.net/",
             "keywords": [
-                "exception"
+                "idn",
+                "idna",
+                "php"
             ],
-            "time": "2015-02-10 20:07:52"
+            "time": "2016-01-06 21:05:46"
         },
         {
             "name": "psr/http-message",
             "time": "2015-12-31 15:58:49"
         },
         {
-            "name": "guzzlehttp/guzzle",
-            "version": "6.2.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/guzzle/guzzle.git",
-                "reference": "d094e337976dff9d8e2424e8485872194e768662"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d094e337976dff9d8e2424e8485872194e768662",
-                "reference": "d094e337976dff9d8e2424e8485872194e768662",
-                "shasum": ""
-            },
-            "require": {
-                "guzzlehttp/promises": "~1.0",
-                "guzzlehttp/psr7": "~1.1",
-                "php": ">=5.5.0"
-            },
-            "require-dev": {
-                "ext-curl": "*",
-                "phpunit/phpunit": "~4.0",
-                "psr/log": "~1.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "6.2-dev"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "src/functions_include.php"
-                ],
-                "psr-4": {
-                    "GuzzleHttp\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Michael Dowling",
-                    "email": "mtdowling@gmail.com",
-                    "homepage": "https://github.com/mtdowling"
-                }
-            ],
-            "description": "Guzzle is a PHP HTTP client library",
-            "homepage": "http://guzzlephp.org/",
-            "keywords": [
-                "client",
-                "curl",
-                "framework",
-                "http",
-                "http client",
-                "rest",
-                "web service"
-            ],
-            "time": "2016-03-21 20:02:09"
-        },
-        {
-            "name": "guzzlehttp/promises",
-            "version": "1.1.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/guzzle/promises.git",
-                "reference": "bb9024c526b22f3fe6ae55a561fd70653d470aa8"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/promises/zipball/bb9024c526b22f3fe6ae55a561fd70653d470aa8",
-                "reference": "bb9024c526b22f3fe6ae55a561fd70653d470aa8",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.5.0"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "~4.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.0-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "GuzzleHttp\\Promise\\": "src/"
-                },
-                "files": [
-                    "src/functions_include.php"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Michael Dowling",
-                    "email": "mtdowling@gmail.com",
-                    "homepage": "https://github.com/mtdowling"
-                }
-            ],
-            "description": "Guzzle promises library",
-            "keywords": [
-                "promise"
-            ],
-            "time": "2016-03-08 01:15:46"
-        },
-        {
-            "name": "guzzlehttp/psr7",
-            "version": "1.2.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/guzzle/psr7.git",
-                "reference": "2e89629ff057ebb49492ba08e6995d3a6a80021b"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/psr7/zipball/2e89629ff057ebb49492ba08e6995d3a6a80021b",
-                "reference": "2e89629ff057ebb49492ba08e6995d3a6a80021b",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.4.0",
-                "psr/http-message": "~1.0"
-            },
-            "provide": {
-                "psr/http-message-implementation": "1.0"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "~4.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.0-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "GuzzleHttp\\Psr7\\": "src/"
-                },
-                "files": [
-                    "src/functions_include.php"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Michael Dowling",
-                    "email": "mtdowling@gmail.com",
-                    "homepage": "https://github.com/mtdowling"
-                }
-            ],
-            "description": "PSR-7 message implementation",
-            "keywords": [
-                "http",
-                "message",
-                "stream",
-                "uri"
-            ],
-            "time": "2016-02-18 21:54:00"
-        },
-        {
             "name": "mikey179/vfsStream",
             "version": "v1.6.2",
             "source": {
index f175496..639f8fe 100644 (file)
@@ -718,7 +718,6 @@ class Clipboard
         return $message;
     }
 
-
     /**
      * Returns confirm JavaScript message
      *
index 70514f9..707d97a 100644 (file)
@@ -535,7 +535,7 @@ class Bootstrap
      */
     protected function defineUserAgentConstant()
     {
-        define('TYPO3_user_agent', 'User-Agent: ' . $GLOBALS['TYPO3_CONF_VARS']['HTTP']['userAgent']);
+        define('TYPO3_user_agent', 'User-Agent: ' . $GLOBALS['TYPO3_CONF_VARS']['HTTP']['headers']['User-Agent']);
         return $this;
     }
 
diff --git a/typo3/sysext/core/Classes/Http/HttpRequest.php b/typo3/sysext/core/Classes/Http/HttpRequest.php
deleted file mode 100644 (file)
index 69b1c14..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-<?php
-namespace TYPO3\CMS\Core\Http;
-
-/*
- * 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!
- */
-
-/**
- * HTTP Request Utility class
- *
- * Extends \HTTP_Request2 and sets TYPO3 environment defaults
- */
-class HttpRequest extends \HTTP_Request2
-{
-    /**
-     * Default constructor - sets TYPO3 defaults
-     *
-     * @param string|\Net_Url2 $url Request URL
-     * @param string $method Request Method (GET, HEAD or POST). Redirects reset this to GET unless "strict_redirects" is set.
-     * @param array $config Configuration for this request instance
-     * @link http://pear.php.net/manual/en/package.http.http-request2.config.php
-     */
-    public function __construct($url = null, $method = self::METHOD_GET, array $config = array())
-    {
-        parent::__construct($url, $method);
-        $this->setConfiguration($config);
-    }
-
-    /**
-     * Sets the configuration for this object instance.
-     * Merges default values with provided $config and overrides all
-     * not provided values with those from $TYPO3_CONF_VARS
-     *
-     * @param array $config Configuration options which override the default configuration
-     * @return void
-     * @see http://pear.php.net/manual/en/package.http.http-request2.config.php
-     */
-    public function setConfiguration(array $config = array())
-    {
-        // set a branded user-agent
-        $this->setHeader('user-agent', $GLOBALS['TYPO3_CONF_VARS']['HTTP']['userAgent']);
-        $default = array(
-            'adapter' => $GLOBALS['TYPO3_CONF_VARS']['HTTP']['adapter'],
-            'connect_timeout' => $GLOBALS['TYPO3_CONF_VARS']['HTTP']['connect_timeout'],
-            'timeout' => $GLOBALS['TYPO3_CONF_VARS']['HTTP']['timeout'],
-            'protocol_version' => $GLOBALS['TYPO3_CONF_VARS']['HTTP']['protocol_version'],
-            'follow_redirects' => (bool)$GLOBALS['TYPO3_CONF_VARS']['HTTP']['follow_redirects'],
-            'max_redirects' => $GLOBALS['TYPO3_CONF_VARS']['HTTP']['max_redirects'],
-            'strict_redirects' => (bool)$GLOBALS['TYPO3_CONF_VARS']['HTTP']['strict_redirects'],
-            'proxy_host' => $GLOBALS['TYPO3_CONF_VARS']['HTTP']['proxy_host'],
-            'proxy_port' => $GLOBALS['TYPO3_CONF_VARS']['HTTP']['proxy_port'],
-            'proxy_user' => $GLOBALS['TYPO3_CONF_VARS']['HTTP']['proxy_user'],
-            'proxy_password' => $GLOBALS['TYPO3_CONF_VARS']['HTTP']['proxy_password'],
-            'proxy_auth_scheme' => $GLOBALS['TYPO3_CONF_VARS']['HTTP']['proxy_auth_scheme'],
-            'ssl_verify_peer' => (bool)$GLOBALS['TYPO3_CONF_VARS']['HTTP']['ssl_verify_peer'],
-            'ssl_verify_host' => (bool)$GLOBALS['TYPO3_CONF_VARS']['HTTP']['ssl_verify_host'],
-            // we have to deal with Install Tool limitations and set this to NULL if it is empty
-            'ssl_cafile' => $GLOBALS['TYPO3_CONF_VARS']['HTTP']['ssl_cafile'] ?: null,
-            'ssl_capath' => $GLOBALS['TYPO3_CONF_VARS']['HTTP']['ssl_capath'] ?: null,
-            'ssl_local_cert' => $GLOBALS['TYPO3_CONF_VARS']['HTTP']['ssl_local_cert'] ?: null,
-            'ssl_passphrase' => $GLOBALS['TYPO3_CONF_VARS']['HTTP']['ssl_passphrase'] ?: null
-        );
-        $configuration = array_merge($default, $config);
-        $this->setConfig($configuration);
-    }
-
-    /**
-     * Downloads chunk by chunk to file instead of saving the whole response into memory.
-     * $response->getBody() will be empty.
-     * An existing file will be overridden.
-     *
-     * @param string $directory The absolute path to the directory in which the file is saved.
-     * @param string $filename The filename - if not set, it is determined automatically.
-     * @return \HTTP_Request2_Response The response with empty body.
-     */
-    public function download($directory, $filename = '')
-    {
-        $isAttached = false;
-        // Do not store the body in memory
-        $this->setConfig('store_body', false);
-        // Check if we already attached an instance of download. If so, just reuse it.
-        foreach ($this->observers as $observer) {
-            if ($observer instanceof Observer\Download) {
-                /** @var Observer\Download $attached */
-                $observer->setDirectory($directory);
-                $observer->setFilename($filename);
-                $isAttached = true;
-            }
-        }
-        if (!$isAttached) {
-            /** @var Observer\Download $observer */
-            $observer = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Http\Observer\Download::class, $directory, $filename);
-            $this->attach($observer);
-        }
-        return $this->send();
-    }
-}
diff --git a/typo3/sysext/core/Classes/Http/Observer/Download.php b/typo3/sysext/core/Classes/Http/Observer/Download.php
deleted file mode 100644 (file)
index 6d2d774..0000000
+++ /dev/null
@@ -1,174 +0,0 @@
-<?php
-namespace TYPO3\CMS\Core\Http\Observer;
-
-/*
- * 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!
- */
-
-/**
- * Observer to automatically save a http request chunk by chunk to a file.
- * If the file already exists, it will be overwritten.
- * This follows an example in HTTP_Request2 manual.
- *
- * @see http://pear.php.net/manual/en/package.http.http-request2.observers.php
- */
-class Download implements \SplObserver
-{
-    /**
-     * @var resource A file pointer resource
-     */
-    protected $filePointer = false;
-
-    /**
-     * @var string The full filename including the leading directory
-     */
-    protected $targetFilePath = '';
-
-    /**
-     * @var string The name of the target directory
-     */
-    protected $targetDirectory = '';
-
-    /**
-     * @var string The name of the target file
-     */
-    protected $targetFilename = '';
-
-    /**
-     * Constructor
-     *
-     * @throws \InvalidArgumentException if directory is not found or is not within the PATH_site
-     * @param string $directory The absolute path to the directory in which the file is saved.
-     * @param string $filename The filename - if not set, it is determined automatically.
-     */
-    public function __construct($directory, $filename = '')
-    {
-        $this->setDirectory($directory);
-        $this->setFilename($filename);
-    }
-
-    /**
-     * Saves current chunk to disk each time a body part is received.
-     * If the filename is empty, tries to determine it from received headers
-     *
-     * @throws \TYPO3\CMS\Core\Exception if file can not be opened
-     * @throws \UnexpectedValueException if the file name is empty and can not be determined from headers
-     * @param \SplSubject|\HTTP_Request2 $request
-     * @return void
-     */
-    public function update(\SplSubject $request)
-    {
-        $event = $request->getLastEvent();
-        switch ($event['name']) {
-            case 'receivedHeaders':
-                if ($this->targetFilename === '') {
-                    $this->determineFilename($request, $event['data']);
-                }
-                $this->openFile();
-                break;
-            case 'receivedBodyPart':
-            case 'receivedEncodedBodyPart':
-                fwrite($this->filePointer, $event['data']);
-                break;
-            case 'receivedBody':
-                $this->closeFile();
-                break;
-            default:
-                // Do nothing
-        }
-    }
-
-    /**
-     * Sets the directory and checks whether the directory is available.
-     *
-     * @throws \InvalidArgumentException if directory is not found or is not within the PATH_site
-     * @param string $directory The absolute path to the directory in which the file is saved.
-     * @return void
-     */
-    public function setDirectory($directory)
-    {
-        if (!is_dir($directory)) {
-            throw new \InvalidArgumentException($directory . ' is not a directory', 1312223779);
-        }
-        if (!\TYPO3\CMS\Core\Utility\GeneralUtility::isAllowedAbsPath($directory)) {
-            throw new \InvalidArgumentException($directory . ' is not within the PATH_site' . ' OR within the lockRootPath', 1328734617);
-        }
-        $this->targetDirectory = ($directory = rtrim($directory, DIRECTORY_SEPARATOR));
-    }
-
-    /**
-     * Sets the filename.
-     *
-     * If the file already exists, it will be overridden
-     *
-     * @param string $filename The filename
-     * @return void
-     */
-    public function setFilename($filename = '')
-    {
-        $this->targetFilename = basename($filename);
-    }
-
-    /**
-     * Determines the filename from either the 'content-disposition' header
-     * or from the basename of the current request.
-     *
-     * @param \HTTP_Request2 $request
-     * @param \HTTP_Request2_Response $response
-     * @return void
-     */
-    protected function determineFilename(\HTTP_Request2 $request, \HTTP_Request2_Response $response)
-    {
-        $matches = array();
-        $disposition = $response->getHeader('content-disposition');
-        if ($disposition !== null && 0 === strpos($disposition, 'attachment') && 1 === preg_match('/filename="([^"]+)"/', $disposition, $matches)) {
-            $filename = basename($matches[1]);
-        } else {
-            $filename = basename($request->getUrl()->getPath());
-        }
-        $this->setFilename($filename);
-    }
-
-    /**
-     * Determines the absolute path to the file by combining the directory and filename.
-     * Afterwards tries to open the file for writing.
-     *
-     * $this->filename must be set before calling this function.
-     *
-     * @throws \UnexpectedValueException if $this->filename is not set
-     * @throws \TYPO3\CMS\Core\Exception if file can not be opened
-     * @return void
-     */
-    protected function openFile()
-    {
-        if ($this->targetFilename === '') {
-            throw new \UnexpectedValueException('The file name must not be empty', 1321113658);
-        }
-        $this->targetFilePath = $this->targetDirectory . DIRECTORY_SEPARATOR . $this->targetFilename;
-        $this->filePointer = @fopen($this->targetFilePath, 'wb');
-        if ($this->filePointer === false) {
-            throw new \TYPO3\CMS\Core\Exception('Cannot open target file ' . $this->targetFilePath, 1320833203);
-        }
-    }
-
-    /**
-     * Closes the file handler and fixes permissions.
-     *
-     * @return void
-     */
-    protected function closeFile()
-    {
-        fclose($this->filePointer);
-        $this->filePointer = false;
-        \TYPO3\CMS\Core\Utility\GeneralUtility::fixPermissions($this->targetFilePath);
-    }
-}
diff --git a/typo3/sysext/core/Classes/Http/RequestFactory.php b/typo3/sysext/core/Classes/Http/RequestFactory.php
new file mode 100644 (file)
index 0000000..b7a8e19
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+namespace TYPO3\CMS\Core\Http;
+
+/*
+ * 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 GuzzleHttp\Client;
+use GuzzleHttp\ClientInterface;
+use Psr\Http\Message\ResponseInterface;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Class RequestFactory to create Request objects
+ * Returns PSR-7 Request objects (currently the Guzzle implementation).
+ */
+class RequestFactory
+{
+    /**
+     * Create a request object with our custom implementation
+     *
+     * @param string $uri the URI to request
+     * @param string $method the HTTP method (defaults to GET)
+     * @param array $options custom options for this request
+     * @return ResponseInterface
+     */
+    public function request(string $uri, string $method = 'GET', array $options = []): ResponseInterface
+    {
+        $client = $this->getClient();
+        return $client->request($method, $uri, $options);
+    }
+
+    /**
+     * Creates the client to do requests
+     * @return ClientInterface
+     */
+    protected function getClient(): ClientInterface
+    {
+        return GeneralUtility::makeInstance(Client::class, $GLOBALS['TYPO3_CONF_VARS']['HTTP']);
+    }
+}
index 2271719..1f44f60 100755 (executable)
@@ -18,6 +18,7 @@ use TYPO3\CMS\Core\Charset\CharsetConverter;
 use TYPO3\CMS\Core\Core\ApplicationContext;
 use TYPO3\CMS\Core\Core\ClassLoadingInformation;
 use TYPO3\CMS\Core\Crypto\Random;
+use TYPO3\CMS\Core\Http\RequestFactory;
 use TYPO3\CMS\Core\Service\OpcodeCacheService;
 use TYPO3\CMS\Core\SingletonInterface;
 use TYPO3\CMS\Frontend\Page\PageRepository;
@@ -1995,7 +1996,7 @@ class GeneralUtility
      *************************/
     /**
      * Reads the file or url $url and returns the content
-     * If you are having trouble with proxys when reading URLs you can configure your way out of that with settings like $GLOBALS['TYPO3_CONF_VARS']['SYS']['curlUse'] etc.
+     * If you are having trouble with proxies when reading URLs you can configure your way out of that with settings within $GLOBALS['TYPO3_CONF_VARS']['HTTP'].
      *
      * @param string $url File/URL to read
      * @param int $includeHeader Whether the HTTP header should be fetched or not. 0=disable, 1=fetch header+content, 2=fetch header only
@@ -2003,203 +2004,63 @@ class GeneralUtility
      * @param array $report Error code/message and, if $includeHeader is 1, response meta data (HTTP status and content type)
      * @return mixed The content from the resource given as input. FALSE if an error has occurred.
      */
-    public static function getUrl($url, $includeHeader = 0, $requestHeaders = false, &$report = null)
+    public static function getUrl($url, $includeHeader = 0, $requestHeaders = null, &$report = null)
     {
-        $content = false;
         if (isset($report)) {
             $report['error'] = 0;
             $report['message'] = '';
         }
-        // Use cURL for: http, https, ftp, ftps, sftp and scp
-        if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['curlUse'] == '1' && preg_match('/^(?:http|ftp)s?|s(?:ftp|cp):/', $url)) {
-            if (isset($report)) {
-                $report['lib'] = 'cURL';
-            }
-            // External URL without error checking.
-            if (!function_exists('curl_init') || !($ch = curl_init())) {
-                if (isset($report)) {
-                    $report['error'] = -1;
-                    $report['message'] = 'Couldn\'t initialize cURL.';
-                }
-                return false;
-            }
-
-            $followLocationSucceeded = @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
-
-            $curlIncludeHeaders = !$followLocationSucceeded || $includeHeader;
-            curl_setopt($ch, CURLOPT_URL, $url);
-            curl_setopt($ch, CURLOPT_HEADER, $curlIncludeHeaders ? 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']));
-
+        // Looks like it's an external file, use Guzzle by default
+        if (preg_match('/^(?:http|ftp)s?|s(?:ftp|cp):/', $url)) {
+            /** @var RequestFactory $requestFactory */
+            $requestFactory = static::makeInstance(RequestFactory::class);
             if (is_array($requestHeaders)) {
-                curl_setopt($ch, CURLOPT_HTTPHEADER, $requestHeaders);
-            }
-            // (Proxy support implemented by Arco <arco@appeltaart.mine.nu>)
-            if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyServer']) {
-                curl_setopt($ch, CURLOPT_PROXY, $GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyServer']);
-                curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, (bool)$GLOBALS['TYPO3_CONF_VARS']['HTTP']['ssl_verify_host']);
-                curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (bool)$GLOBALS['TYPO3_CONF_VARS']['HTTP']['ssl_verify_peer']);
-                if ($GLOBALS['TYPO3_CONF_VARS']['HTTP']['ssl_verify_peer']) {
-                    if ($GLOBALS['TYPO3_CONF_VARS']['HTTP']['ssl_cafile']) {
-                        curl_setopt($ch, CURLOPT_CAINFO, $GLOBALS['TYPO3_CONF_VARS']['HTTP']['ssl_cafile']);
-                    }
-                    if ($GLOBALS['TYPO3_CONF_VARS']['HTTP']['ssl_capath']) {
-                        curl_setopt($ch, CURLOPT_CAPATH, $GLOBALS['TYPO3_CONF_VARS']['HTTP']['ssl_capath']);
-                    }
-                }
-                if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyNTLM']) {
-                    curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_NTLM);
-                }
-                if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyTunnel']) {
-                    curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, $GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyTunnel']);
-                }
-                if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyUserPass']) {
-                    curl_setopt($ch, CURLOPT_PROXYUSERPWD, $GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyUserPass']);
-                }
-            }
-            $content = curl_exec($ch);
-            $curlInfo = curl_getinfo($ch);
-
-            // Remove additional proxy header block, when proxy is used for https request and CURL_HEADER is enabled.
-            // Most HTTPS proxies add a second header before the actual server headers in their response, as a
-            // response to the CONNECT message sent by the client to the proxy. cURL does not strip this since 2005,
-            // so there are two headers arriving here, of which the first is not of interest to us—therefore, we can
-            // safely strip it.
-            // Detecting two linebreaks followed by a "HTTP/" (as done here) is the only reliable way to detect the
-            // proxy headers, as the relevant RFCs do not specify the exact status code (it might be any of 2xx) or
-            // the status message. Therefore, we check if there is a second HTTP headers block and then strip the
-            // first one.
-            if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyServer']
-                && $curlIncludeHeaders
-                && preg_match('/^https:/', $url)
-                && strpos($content, "\r\n\r\nHTTP/") !== false
-            ) {
-                $content = self::stripHttpHeaders($content);
+                $configuration = ['headers' => $requestHeaders];
+            } else {
+                $configuration = [];
             }
 
-            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;
-                    }
+            $response = $requestFactory->request($url, 'GET', $configuration);
+            $content = '';
+
+            // Add the headers to the output
+            $includeHeader = (int)$includeHeader;
+            if ($includeHeader) {
+                $parsedURL = parse_url($url);
+                $method = $includeHeader === 2 ? 'HEAD' : 'GET';
+                $content = $method . ' ' . (isset($parsedURL['path']) ? $parsedURL['path'] : '/')
+                    . ($parsedURL['query'] ? '?' . $parsedURL['query'] : '') . ' HTTP/1.0' . CRLF
+                    . 'Host: ' . $parsedURL['host'] . CRLF
+                    . 'Connection: close' . CRLF;
+                if (is_array($requestHeaders)) {
+                    $content .= implode(CRLF, $requestHeaders) . CRLF;
                 }
-                if ($content && !$includeHeader) {
-                    $content = self::stripHttpHeaders($content);
+                foreach ($response->getHeaders() as $headerName => $headerValues) {
+                    $content .= $headerName . ': ' . implode(', ', $headerValues) . CRLF;
                 }
+                // Headers are separated from the body with two CRLFs
+                $content .= CRLF;
+            }
+            // If not just headers are requested, add the body
+            if ($includeHeader !== 2) {
+                $content .= $response->getBody()->getContents();
             }
-
             if (isset($report)) {
-                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);
+                $report['lib'] = 'http';
+                if ($response->getStatusCode() >= 300 && $response->getStatusCode() < 400) {
+                    $report['http_code'] = $response->getStatusCode();
+                    $report['content_type'] = $response->getHeader('Content-type');
+                    $report['error'] = $response->getStatusCode();
+                    $report['message'] = $response->getReasonPhrase();
+                } elseif (!empty($content)) {
+                    $report['error'] = $response->getStatusCode();
+                    $report['message'] = $response->getReasonPhrase();
                 } 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);
-        } elseif ($includeHeader) {
-            if (isset($report)) {
-                $report['lib'] = 'socket';
-            }
-            $parsedURL = parse_url($url);
-            if (!preg_match('/^https?/', $parsedURL['scheme'])) {
-                if (isset($report)) {
-                    $report['error'] = -1;
-                    $report['message'] = 'Reading headers is not allowed for this protocol.';
-                }
-                return false;
-            }
-            $port = (int)$parsedURL['port'];
-            if ($port < 1) {
-                if ($parsedURL['scheme'] == 'http') {
-                    $port = $port > 0 ? $port : 80;
-                    $scheme = '';
-                } else {
-                    $port = $port > 0 ? $port : 443;
-                    $scheme = 'ssl://';
-                }
-            }
-            $errno = 0;
-            $fp = @fsockopen(($scheme . $parsedURL['host']), $port, $errno, $errstr, 2.0);
-            if (!$fp || $errno > 0) {
-                if (isset($report)) {
-                    $report['error'] = $errno ?: -1;
-                    $report['message'] = $errno ? ($errstr ?: 'Socket error.') : 'Socket initialization error.';
-                }
-                return false;
-            }
-            $method = $includeHeader == 2 ? 'HEAD' : 'GET';
-            $msg = $method . ' ' . (isset($parsedURL['path']) ? $parsedURL['path'] : '/')
-                   . ($parsedURL['query'] ? '?' . $parsedURL['query'] : '') . ' HTTP/1.0' . CRLF
-                   . 'Host: ' . $parsedURL['host'] . CRLF
-                   . 'Connection: close' . CRLF;
-            if (is_array($requestHeaders)) {
-                $msg .= implode(CRLF, $requestHeaders) . CRLF;
-            }
-            $msg .= CRLF;
-            fputs($fp, $msg);
-            while (!feof($fp)) {
-                $line = fgets($fp, 2048);
-                if (isset($report)) {
-                    if (preg_match('|^HTTP/\\d\\.\\d +(\\d+)|', $line, $status)) {
-                        $report['http_code'] = $status[1];
-                    } elseif (preg_match('/^Content-Type: *(.*)/i', $line, $type)) {
-                        $report['content_type'] = $type[1];
-                    }
-                }
-                $content .= $line;
-                if (trim($line) === '') {
-                    // Stop at the first empty line (= end of header)
-                    break;
+                    $report['http_code'] = $response->getStatusCode();
+                    $report['content_type'] = $response->getHeader('Content-type');
                 }
             }
-            if ($includeHeader != 2) {
-                $content .= stream_get_contents($fp);
-            }
-            fclose($fp);
-        } elseif (is_array($requestHeaders)) {
-            if (isset($report)) {
-                $report['lib'] = 'file/context';
-            }
-            $parsedURL = parse_url($url);
-            if (!preg_match('/^https?/', $parsedURL['scheme'])) {
-                if (isset($report)) {
-                    $report['error'] = -1;
-                    $report['message'] = 'Sending request headers is not allowed for this protocol.';
-                }
-                return false;
-            }
-            $ctx = stream_context_get_default(array(
-                'http' => array(
-                    'header' => implode(CRLF, $requestHeaders)
-                )
-            ));
-            $content = @file_get_contents($url, false, $ctx);
-            if ($content === false && isset($report)) {
-                $report['error'] = -1;
-                $report['message'] = 'Couldn\'t get URL: ' . (isset($http_response_header) ? implode(LF, $http_response_header) : $url);
-            }
         } else {
             if (isset($report)) {
                 $report['lib'] = 'file';
@@ -2207,54 +2068,13 @@ class GeneralUtility
             $content = @file_get_contents($url);
             if ($content === false && isset($report)) {
                 $report['error'] = -1;
-                $report['message'] = 'Couldn\'t get URL: ' . (isset($http_response_header) ? implode(LF, $http_response_header) : $url);
+                $report['message'] = 'Couldn\'t get URL: ' . $url;
             }
         }
         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
-     */
-    protected static 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
-     */
-    protected static 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
      *
      * @param string $file Filepath to write to
index 384f9cf..d5e746d 100644 (file)
@@ -69,24 +69,6 @@ return array(
         'USdateFormat' => false,                // Boolean: If TRUE, dates entered in the TCEforms of the backend will be formatted mm-dd-yyyy
         'loginCopyrightWarrantyProvider' => '',    // String: If you provide warranty for TYPO3 to your customers insert you (company) name here. It will appear in the login-dialog as the warranty provider. (You must also set URL below).
         'loginCopyrightWarrantyURL' => '',        // String: Add the URL where you explain the extend of the warranty you provide. This URL is displayed in the login dialog as the place where people can learn more about the conditions of your warranty. Must be set (more than 10 chars) in addition with the 'loginCopyrightWarrantyProvider' message.
-        'curlUse' => false,                        // Boolean: If set, try to use cURL to fetch external URLs
-        'curlProxyNTLM' => false,                    // Boolean: Proxy NTLM authentication support.
-        /**
-         * @deprecated since 4.6 - will be removed in 6.2.
-         */
-        'curlProxyServer' => '',                // String: Proxyserver as http://proxy:port/. Deprecated since 4.6 - will be removed in 6.2. See below for http options.
-        /**
-         * @deprecated since 4.6 - will be removed in 6.2.
-         */
-        'curlProxyTunnel' => false,                // Boolean: If set, use a tunneled connection through the proxy (useful for websense etc.). Deprecated since 4.6 - will be removed in 6.2. See below for http options.
-        /**
-         * @deprecated since 4.6 - will be removed in 6.2.
-         */
-        'curlProxyUserPass' => '',                // String: Proxyserver authentication user:pass. Deprecated since 4.6 - will be removed in 6.2. See below for http options.
-        /**
-         * @deprecated since 4.6 - will be removed in 6.2.
-         */
-        'curlTimeout' => 0,                        // Integer: Timeout value for cURL requests in seconds. 0 means to wait indefinitely. Deprecated since 4.6 - will be removed in 6.2. See below for http options.
         'textfile_ext' => 'txt,ts,typoscript,html,htm,css,tmpl,js,sql,xml,csv,xlf',    // Text file extensions. Those that can be edited. Executable PHP files may not be editable in webspace if disallowed!
         'mediafile_ext' => 'gif,jpg,jpeg,bmp,png,pdf,svg,ai,mp3,wav,mp4,webm,youtube,vimeo',    // Commalist of file extensions perceived as media files by TYPO3. Lowercase and no spaces between!
         'binPath' => '',                        // String: List of absolute paths where external programs should be searched for. Eg. <code>/usr/local/webbin/,/home/xyz/bin/</code>. (ImageMagick path have to be configured separately)
@@ -1136,26 +1118,21 @@ return array(
         'defaultMailFromAddress' => '',        // String: This default email address is used when no other "from" address is set for a TYPO3-generated email. You can specify an email address only (ex. info@example.org).
         'defaultMailFromName' => ''// String: This default name is used when no other "from" name is set for a TYPO3-generated email.
     ),
-    'HTTP' => array( // HTTP configuration to tune how TYPO3 behaves on HTTP request. Have a look at <a href="http://pear.php.net/manual/en/package.http.http-request2.config.php>HTTP_Request2 Manual</a> for some background information on those settings.
-        'adapter' => 'socket',        // String: Default adapter - either "socket" or "curl".
+    'HTTP' => array(    // HTTP configuration to tune how TYPO3 behaves on HTTP requests made by TYPO3. Have a look at http://docs.guzzlephp.org/en/latest/request-options.html for some background information on those settings.
+        'allow_redirects' => array( // Mixed, set to false if you want to allow redirects, or use it as an array to add more values, see http://docs.guzzlephp.org/en/latest/request-options.html#allow-redirects for syntax
+            'max' => 5,        // Integer: Maximum number of tries before an exception is thrown.
+            'strict' => false        // Boolean: Whether to keep request method on redirects via status 301 and 302 (TRUE, needed for compatibility with <a href="http://www.faqs.org/rfcs/rfc2616">RFC 2616</a>) or switch to GET (FALSE, needed for compatibility with most browsers).
+        ),
+        'cert' => null,  // Mixed: Set to a string to specify the path to a file containing a PEM formatted client side certificate. See http://docs.guzzlephp.org/en/latest/request-options.html#cert
         'connect_timeout' => 10,        // Integer: Default timeout for connection. Exception will be thrown if connecting to remote host takes more than this number of seconds.
+        'proxy' => null,        // Mixed: Default proxy server as "proxy.example.org", multiple proxies for different protocols can be added separately as array, as well as authentication and port, see http://docs.guzzlephp.org/en/latest/request-options.html#proxy
+        'ssl_key' => null,        // Mixed: Local certificate and an optional passphrase, see http://docs.guzzlephp.org/en/latest/request-options.html#ssl-key
         'timeout' => 0,        // Integer: Default timeout for whole request. Exception will be thrown if sending the request takes more than this number of seconds. Should be greater than connection timeout (see above) or "0" to not set a limit. Defaults to "0".
-        'protocol_version' => '1.1',        // String: Default HTTP protocol version. Use either "1.0" or "1.1".
-        'follow_redirects' => false,        // Boolean: If set, redirects are followed by default. If number of tries are exceeded, an exception is thrown.
-        'max_redirects' => 5,        // Integer: Maximum number of tries before an exception is thrown.
-        'strict_redirects' => false,        // Boolean: Whether to keep request method on redirects via status 301 and 302 (TRUE, needed for compatibility with <a href="http://www.faqs.org/rfcs/rfc2616">RFC 2616</a>) or switch to GET (FALSE, needed for compatibility with most browsers). There are some <a href="http://pear.php.net/manual/en/package.http.http-request2.adapters.php#package.http.http-request2.adapters.curl">issues with cURL adapter</a>. Defaults to FALSE.
-        'proxy_host' => '',        // String: Default proxy server as "proxy.example.org" (You must not set the protocol or the port here. Set the port below.)
-        'proxy_port' => '',        // Integer: Default proxy server port.
-        'proxy_user' => '',        // String: Default user name.
-        'proxy_password' => '',        // String: Default password.
-        'proxy_auth_scheme' => 'basic',        // String: Default authentication method. Can either be "basic" or "digest". Defaults to "basic".
-        'ssl_verify_peer' => false,        // Boolean: Whether to verify peer's SSL certificate. Turned off by default, due to <a href="http://pear.php.net/manual/en/package.http.http-request2.adapters.php#package.http.http-request2.adapters.socket" target="_blank">issues with Socket adapter</a>. You are advised to use the <em>curl</em> adapter and enable this option!
-        'ssl_verify_host' => true,        // Boolean: Whether to check that Common Name in SSL certificate matches hostname. There are some <a href="http://pear.php.net/manual/en/package.http.http-request2.adapters.php#package.http.http-request2.adapters.socket" target="_blank">issues with Socket Adapter</a>.
-        'ssl_cafile' => '',        // String: Certificate Authority file to verify the peer with (use when ssl_verify_peer is TRUE).
-        'ssl_capath' => '',        // String: Directory holding multiple Certificate Authority files.
-        'ssl_local_cert' => '',        // String: Name of a file containing local certificate.
-        'ssl_passphrase' => '',        // String: Passphrase with which local certificate was encoded.
-        'userAgent' => 'TYPO3/' . TYPO3_version// String: Default user agent. If empty, this will be "TYPO3/x.y.z", while x.y.z is the current version. This overrides the constant <em>TYPO3_user_agent</em>.
+        'verify' => true,       // Mixed: Describes the SSL certificate verification behavior of a request. http://docs.guzzlephp.org/en/latest/request-options.html#verify
+        'version' => '1.1',        // String: Default HTTP protocol version. Use either "1.0" or "1.1".
+        'headers' => array( // Additional HTTP headers sent by every request TYPO3 executes.
+            'User-Agent' => 'TYPO3/' . TYPO3_version // String: Default user agent. If empty, this will be "TYPO3/x.y.z", while x.y.z is the current version. This overrides the constant <em>TYPO3_user_agent</em>.
+        )
     ),
     'LOG' => array(
         'writerConfiguration' => array(
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-70056-CurlAndHttpRequestRemoved.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-70056-CurlAndHttpRequestRemoved.rst
new file mode 100644 (file)
index 0000000..644cee9
--- /dev/null
@@ -0,0 +1,81 @@
+=====================================================================
+Breaking: #70056 - Http-related options and HttpRequest class removed
+=====================================================================
+
+Description
+===========
+
+The following PHP classes have been removed:
+
+   * ``TYPO3\CMS\Core\Http\HttpRequest``
+   * ``TYPO3\CMS\Core\Http\Observer\Download``
+
+The following configuration options have been removed:
+
+   * $TYPO3_CONF_VARS[SYS][curlUse]
+   * $TYPO3_CONF_VARS[SYS][curlProxyNTLM]
+   * $TYPO3_CONF_VARS[SYS][curlProxyServer]
+   * $TYPO3_CONF_VARS[SYS][curlProxyTunnel]
+   * $TYPO3_CONF_VARS[SYS][curlProxyUserPass]
+   * $TYPO3_CONF_VARS[SYS][curlTimeout]
+   * $TYPO3_CONF_VARS[HTTP][adapter]
+   * $TYPO3_CONF_VARS[HTTP][protocol_version]
+   * $TYPO3_CONF_VARS[HTTP][follow_redirects]
+   * $TYPO3_CONF_VARS[HTTP][max_redirects]
+   * $TYPO3_CONF_VARS[HTTP][strict_redirects]
+   * $TYPO3_CONF_VARS[HTTP][proxy_host]
+   * $TYPO3_CONF_VARS[HTTP][proxy_port]
+   * $TYPO3_CONF_VARS[HTTP][proxy_user]
+   * $TYPO3_CONF_VARS[HTTP][proxy_password]
+   * $TYPO3_CONF_VARS[HTTP][proxy_auth_scheme]
+   * $TYPO3_CONF_VARS[HTTP][ssl_verify_peer]
+   * $TYPO3_CONF_VARS[HTTP][ssl_verify_host]
+   * $TYPO3_CONF_VARS[HTTP][ssl_cafile]
+   * $TYPO3_CONF_VARS[HTTP][ssl_capath]
+   * $TYPO3_CONF_VARS[HTTP][ssl_local_cert]
+   * $TYPO3_CONF_VARS[HTTP][ssl_passphrase]
+   * $TYPO3_CONF_VARS[HTTP][userAgent]
+
+The following properties have been renamed:
+
+   * $TYPO3_CONF_VARS[HTTP][userAgent] is now called $TYPO3_CONF_VARS[HTTP][headers][User-Agent]
+   * $TYPO3_CONF_VARS[HTTP][protocol_version] is now called $TYPO3_CONF_VARS[HTTP][version]
+   * All proxy-related options are unified within $TYPO3_CONF_VARS[HTTP][proxy]
+   * All redirect-related options (HTTP/follow_redirects, HTTP/max_redirects, HTTP/strict_redirects) are unified within $TYPO3_CONF_VARS[HTTP][allow_redirects]
+   * All options related to SSL private keys (HTTP/ssl_local_cert, HTTP/ssl_passphrase) are merged into $TYPO3_CONF_VARS[HTTP][ssl_key]
+   * All options related to verify SSL peers are merged into $TYPO3_CONF_VARS[HTTP][verify]
+
+Additionally, the dependency to the PEAR Package "Http_Request2" (composer package name ``pear/http_request2``) has
+been removed in favor of the PHP library Guzzle.
+
+
+Impact
+======
+
+Calling the mentioned classes above will result in a fatal PHP error.
+
+Using the options in custom PHP code will result in unexpected behaviour as the options are non-existent and empty.
+
+PHP code depending on the removed PEAR library "Http_Request2" will result in unexpected behaviour and possibly in a
+fatal PHP error.
+
+
+Affected Installations
+======================
+
+All 3rd party extensions calling the mentioned classes directly or using the configuration options directly, as well
+as installations depending on the PEAR libary "Http_Request2".
+
+
+Migration
+=========
+
+For PHP code previously using the HttpRequest and Download classes a new object-oriented PSR-7-based approach is
+introduced, see the Guzzle Feature integration documentation for more details. A new PHP class
+``TYPO3\CMS\Core\Http\RequestFactory`` which generates PSR-7 compliant request objects, helps in simplifying the
+migration process.
+
+All still necessary options are migrated to new options within ``$TYPO3_CONF_VARS[HTTP]`` when the install tool is run.
+
+In special cases, the options ``$TYPO3_CONF_VARS[HTTP][ssl_verify_host]``, ``$TYPO3_CONF_VARS[HTTP][proxy_auth_scheme]``
+and ``$TYPO3_CONF_VARS[HTTP][proxy_host]`` need to migrated manually to the newly available options.
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-70056-GuzzleForHttpRequests.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-70056-GuzzleForHttpRequests.rst
new file mode 100644 (file)
index 0000000..46fe1d9
--- /dev/null
@@ -0,0 +1,61 @@
+===========================================================================
+Feature: #70056 - Added PHP library "Guzzle" for HTTP Requests within TYPO3
+===========================================================================
+
+Description
+===========
+
+The PHP library ``Guzzle`` is added as a composer dependency as a feature rich solution for creating HTTP requests
+based on the PSR-7 interfaces already used within TYPO3.
+
+Guzzle auto-detects available underlying adapters available on the system, like cURL or stream wrappers and chooses
+the best solution for the system.
+
+A TYPO3-specific PHP class called ``TYPO3\CMS\Core\Http\RequestFactory`` is added as a simplified wrapper to access
+Guzzle clients.
+
+All options available under ``$TYPO3_CONF_VARS[HTTP]`` are automatically applied to the Guzzle clients when using the
+RequestFactory class. The options are a subset to the available options on Guzzle (http://docs.guzzlephp.org/en/latest/request-options.html)
+but can further be extended.
+
+Existing ``$TYPO3_CONF_VARS[HTTP]`` options are removed and/or migrated to the new Guzzle-compliant options.
+
+A full documentation for Guzzle can be found at http://docs.guzzlephp.org/en/latest/.
+
+Although Guzzle can handle Promises/A+ and asynchronous requests, it currently acts as a drop-in replacement for the
+previous mixed options and implementations within ``GeneralUtility::getUrl()`` and a PSR-7-based API for HTTP
+requests.
+
+
+Impact
+======
+
+The existing TYPO3-specific wrapper ``GeneralUtility::getUrl()`` now uses Guzzle under the hood automatically for remote
+files, removing the need to configure settings based on certain implementations like stream wrappers or cURL directly.
+
+The ``RequestFactory`` class can be used like this:
+
+.. code-block:: php
+
+   // Initiate the Request Factory, which allows to run multiple requests
+   /** @var \TYPO3\CMS\Core\Http\RequestFactory $requestFactory */
+   $requestFactory = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Http\RequestFactory\RequestFactory::class);
+   $uri =
+   $additionalOptions = [
+      // Additional headers for this specific request
+      'headers' => ['Cache-Control' => 'no-cache'],
+      // Additional options, see http://docs.guzzlephp.org/en/latest/request-options.html
+      'allow_redirects' => false,
+      'cookies' => true
+   ];
+   // Return a PSR-7 compliant response object
+   $response = $requestFactory->request($url, 'GET', $additionalOptions);
+   // Get the content as a string on a successful request
+   if ($response->getStatusCode() === 200) {
+      if ($response->getHeader('Content-Type') === 'text/html') {
+         $content = $response->getBody()->getContents();
+      }
+   }
+
+Extension authors are advised to use the Request Factory instead of using the Guzzle API directly in order to ensure
+a clear upgrade path when updates to the underlying API need to be done.
diff --git a/typo3/sysext/core/Tests/Unit/Http/HttpRequestTest.php b/typo3/sysext/core/Tests/Unit/Http/HttpRequestTest.php
deleted file mode 100644 (file)
index 0b3c61f..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-namespace TYPO3\CMS\Core\Tests\Unit\Html;
-
-/*
- * 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 TYPO3\CMS\Core\Tests\UnitTestCase;
-
-/**
- * Testcase for \TYPO3\CMS\Core\Http\HttpRequest
- */
-class HttpRequestTest extends UnitTestCase
-{
-    /**
-     * @test
-     */
-    public function pearLibraryCanBeInstantiated()
-    {
-        $this->assertInstanceOf('HTTP_Request2', new \HTTP_Request2());
-    }
-}
index e533d9e..0f78446 100644 (file)
@@ -59,29 +59,6 @@ class GeneralUtilityFixture extends GeneralUtility
     }
 
     /**
-     * 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
-     */
-    public static function getRedirectUrlFromHttpHeaders($content)
-    {
-        return parent::getRedirectUrlFromHttpHeaders($content);
-    }
-
-    /**
-     * Strips HTTP headers from the content.
-     *
-     * @param string $content
-     * @return string
-     */
-    public static function stripHttpHeaders($content)
-    {
-        return parent::stripHttpHeaders($content);
-    }
-
-    /**
      * Gets the absolute path to the deprecation log file.
      *
      * @return string Absolute path to the deprecation log file
index e15ebdd..2931cd8 100644 (file)
@@ -4527,50 +4527,6 @@ class GeneralUtilityTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
     }
 
     /**
-     * @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));
-    }
-
-    /**
      * @test
      */
     public function getAllFilesAndFoldersInPathReturnsArrayWithMd5Keys()
index ce74975..44b1eb2 100644 (file)
@@ -14,6 +14,7 @@ namespace TYPO3\CMS\Documentation\Service;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Http\RequestFactory;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
@@ -181,11 +182,11 @@ class DocumentationService
         }
 
         if (!$hasArchive) {
-            /** @var $http \TYPO3\CMS\Core\Http\HttpRequest */
-            $http = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Http\HttpRequest::class, $packageUrl);
-            $response = $http->send();
-            if ($response->getStatus() == 200) {
-                GeneralUtility::writeFileToTypo3tempDir($absolutePathToZipFile, $response->getBody());
+            /** @var RequestFactory $requestFactory */
+            $requestFactory = GeneralUtility::makeInstance(RequestFactory::class);
+            $response = $requestFactory->request($packageUrl, 'GET');
+            if ($response->getStatusCode() === 200) {
+                GeneralUtility::writeFileToTypo3tempDir($absolutePathToZipFile, $response->getBody()->getContents());
             }
         }
 
index a24913f..ec88583 100644 (file)
@@ -62,7 +62,7 @@ class TerUtility
         }
         $extensionPath = \TYPO3\CMS\Core\Utility\GeneralUtility::strtolower($extensionKey);
         $mirrorUrl .= $extensionPath[0] . '/' . $extensionPath[1] . '/' . $extensionPath . '_' . $version . '.t3x';
-        $t3x = \TYPO3\CMS\Core\Utility\GeneralUtility::getUrl($mirrorUrl, 0, array(TYPO3_user_agent));
+        $t3x = \TYPO3\CMS\Core\Utility\GeneralUtility::getUrl($mirrorUrl);
         $md5 = md5($t3x);
         if ($t3x === false) {
             throw new ExtensionManagerException(sprintf('The T3X file "%s" could not be fetched. Possible reasons: network problems, allow_url_fopen is off, cURL is not enabled in Install Tool.', $mirrorUrl), 1334426097);
index 38ea390..6e998b0 100644 (file)
@@ -148,7 +148,7 @@ class Helper implements \TYPO3\CMS\Core\SingletonInterface
             throw new ExtensionManagerException('Extension Manager is in offline mode. No TER connection available.', 1437078780);
         }
         if (is_string($remoteResource) && is_string($localResource) && !empty($remoteResource) && !empty($localResource)) {
-            $fileContent = \TYPO3\CMS\Core\Utility\GeneralUtility::getUrl($remoteResource, 0, array(TYPO3_user_agent));
+            $fileContent = \TYPO3\CMS\Core\Utility\GeneralUtility::getUrl($remoteResource);
             if ($fileContent !== false) {
                 if (\TYPO3\CMS\Core\Utility\GeneralUtility::writeFileToTypo3tempDir($localResource, $fileContent) !== null) {
                     throw new ExtensionManagerException(sprintf('Could not write to file %s.', $localResource), 1342635378);
@@ -268,7 +268,7 @@ class Helper implements \TYPO3\CMS\Core\SingletonInterface
         if (!is_file($this->getLocalExtListFile())) {
             $updateNecessity |= self::PROBLEM_EXTENSION_FILE_NOT_EXISTING;
         } else {
-            $remotemd5 = \TYPO3\CMS\Core\Utility\GeneralUtility::getUrl($this->getRemoteExtHashFile(), 0, array(TYPO3_user_agent));
+            $remotemd5 = \TYPO3\CMS\Core\Utility\GeneralUtility::getUrl($this->getRemoteExtHashFile());
             if ($remotemd5 !== false) {
                 $localmd5 = md5_file($this->getLocalExtListFile());
                 if ($remotemd5 !== $localmd5) {
index 9e1ff85..f5bb3dd 100755 (executable)
@@ -15,10 +15,10 @@ namespace TYPO3\CMS\Install\Service;
  */
 
 use TYPO3\CMS\Core\Configuration\ConfigurationManager;
+use TYPO3\CMS\Core\Crypto\Random;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Install\Controller\Exception\RedirectException;
-use TYPO3\CMS\Core\Crypto\Random;
 
 /**
  * Execute "silent" LocalConfiguration upgrades if needed.
@@ -87,6 +87,13 @@ class SilentConfigurationUpgradeService
         'FE/XCLASS',
         // #43085
         'GFX/image_processing',
+        // #70056
+        'SYS/curlUse',
+        'SYS/curlProxyNTLM',
+        'SYS/curlProxyServer',
+        'SYS/curlProxyTunnel',
+        'SYS/curlProxyUserPass',
+        'SYS/curlTimeout',
     );
 
     public function __construct(ConfigurationManager $configurationManager = null)
@@ -105,9 +112,8 @@ class SilentConfigurationUpgradeService
         $this->generateEncryptionKeyIfNeeded();
         $this->configureBackendLoginSecurity();
         $this->configureSaltedPasswords();
-        $this->setProxyAuthScheme();
         $this->migrateImageProcessorSetting();
-        $this->transferDeprecatedCurlSettings();
+        $this->transferHttpSettings();
         $this->disableImageMagickDetailSettingsIfImageMagickIsDisabled();
         $this->setImageMagickDetailSettings();
         $this->removeObsoleteLocalConfigurationSettings();
@@ -220,83 +226,189 @@ class SilentConfigurationUpgradeService
     }
 
     /**
-     * $GLOBALS['TYPO3_CONF_VARS']['HTTP']['proxy_auth_scheme'] must be either
-     * 'digest' or 'basic'. 'basic' is default in DefaultConfiguration, so the
-     * setting can be removed from LocalConfiguration if it is not set to 'digest'.
+     * Parse old curl and HTTP options and set new HTTP options, related to Guzzle
      *
      * @return void
      */
-    protected function setProxyAuthScheme()
+    protected function transferHttpSettings()
     {
-        // Get current value from LocalConfiguration
+        $changed = false;
+        $newParameters = [];
+        $obsoleteParameters = [];
+
+        // Remove / migrate options to new options
         try {
-            $currentValueInLocalConfiguration = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/proxy_auth_scheme');
+            // Check if the adapter option is set, if so, set it to the parameters that are obsolete
+            $this->configurationManager->getLocalConfigurationValueByPath('HTTP/adapter');
+            $obsoleteParameters[] = 'HTTP/adapter';
         } catch (\RuntimeException $e) {
-            // If an exception is thrown, the value is not set in LocalConfiguration, so we don't need to do anything
-            return;
         }
-        if ($currentValueInLocalConfiguration !== 'digest') {
-            $this->configurationManager->removeLocalConfigurationKeysByPath(array('HTTP/proxy_auth_scheme'));
-            $this->throwRedirectException();
+        try {
+            $newParameters['HTTP/version'] = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/protocol_version');
+            $obsoleteParameters[] = 'HTTP/protocol_version';
+        } catch (\RuntimeException $e) {
+        }
+        try {
+            $this->configurationManager->getLocalConfigurationValueByPath('HTTP/ssl_verify_host');
+            $obsoleteParameters[] = 'HTTP/ssl_verify_host';
+        } catch (\RuntimeException $e) {
+        }
+        try {
+            $legacyUserAgent = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/userAgent');
+            $newParameters['HTTP/headers/User-Agent'] = $legacyUserAgent;
+            $obsoleteParameters[] = 'HTTP/userAgent';
+        } catch (\RuntimeException $e) {
         }
-    }
 
-    /**
-     * Parse old curl options and set new http ones instead
-     *
-     * @return void
-     */
-    protected function transferDeprecatedCurlSettings()
-    {
-        $changed = false;
+        // Redirects
         try {
-            $curlProxyServer = $this->configurationManager->getLocalConfigurationValueByPath('SYS/curlProxyServer');
+            $legacyFollowRedirects = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/follow_redirects');
+            $obsoleteParameters[] = 'HTTP/follow_redirects';
         } catch (\RuntimeException $e) {
-            $curlProxyServer = '';
+            $legacyFollowRedirects = '';
         }
         try {
-            $proxyHost = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/proxy_host');
+            $legacyMaximumRedirects = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/max_redirects');
+            $obsoleteParameters[] = 'HTTP/max_redirects';
         } catch (\RuntimeException $e) {
-            $proxyHost = '';
+            $legacyMaximumRedirects = '';
         }
-        if (!empty($curlProxyServer) && empty($proxyHost)) {
-            $curlProxy = rtrim(preg_replace('#^https?://#', '', $curlProxyServer), '/');
-            $proxyParts = GeneralUtility::revExplode(':', $curlProxy, 2);
-            $this->configurationManager->setLocalConfigurationValueByPath('HTTP/proxy_host', $proxyParts[0]);
-            $this->configurationManager->setLocalConfigurationValueByPath('HTTP/proxy_port', $proxyParts[1]);
-            $changed = true;
+        try {
+            $legacyStrictRedirects = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/strict_redirects');
+            $obsoleteParameters[] = 'HTTP/strict_redirects';
+        } catch (\RuntimeException $e) {
+            $legacyStrictRedirects = '';
         }
 
+        // Check if redirects have been disabled
+        if ($legacyFollowRedirects !== '' && (bool)$legacyFollowRedirects === false) {
+            $newParameters['HTTP/allow_redirects'] = false;
+        } elseif ($legacyMaximumRedirects !== '' || $legacyStrictRedirects !== '') {
+            $newParameters['HTTP/allow_redirects'] = [];
+            if ($legacyMaximumRedirects !== '' && (int)$legacyMaximumRedirects !== 5) {
+                $newParameters['HTTP/allow_redirects']['max'] = (int)$legacyMaximumRedirects;
+            }
+            if ($legacyStrictRedirects !== '' && (bool)$legacyStrictRedirects === true) {
+                $newParameters['HTTP/allow_redirects']['strict'] = true;
+            }
+            // defaults are used, no need to set the option in LocalConfiguration.php
+            if (empty($newParameters['HTTP/allow_redirects'])) {
+                unset($newParameters['HTTP/allow_redirects']);
+            }
+        }
+
+        // Migrate Proxy settings
         try {
-            $curlProxyUserPass = $this->configurationManager->getLocalConfigurationValueByPath('SYS/curlProxyUserPass');
+            // Currently without protocol or port
+            $legacyProxyHost = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/proxy_host');
+            $obsoleteParameters[] = 'HTTP/proxy_host';
         } catch (\RuntimeException $e) {
-            $curlProxyUserPass = '';
+            $legacyProxyHost = '';
         }
         try {
-            $proxyUser = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/proxy_user');
+            $legacyProxyPort = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/proxy_port');
+            $obsoleteParameters[] = 'HTTP/proxy_port';
         } catch (\RuntimeException $e) {
-            $proxyUser = '';
+            $legacyProxyPort = '';
         }
-        if (!empty($curlProxyUserPass) && empty($proxyUser)) {
-            $userPassParts = explode(':', $curlProxyUserPass, 2);
-            $this->configurationManager->setLocalConfigurationValueByPath('HTTP/proxy_user', $userPassParts[0]);
-            $this->configurationManager->setLocalConfigurationValueByPath('HTTP/proxy_password', $userPassParts[1]);
-            $changed = true;
+        try {
+            $legacyProxyUser = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/proxy_user');
+            $obsoleteParameters[] = 'HTTP/proxy_user';
+        } catch (\RuntimeException $e) {
+            $legacyProxyUser = '';
+        }
+        try {
+            $legacyProxyPassword = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/proxy_password');
+            $obsoleteParameters[] = 'HTTP/proxy_password';
+        } catch (\RuntimeException $e) {
+            $legacyProxyPassword = '';
+        }
+        // Auth Scheme: Basic, digest etc.
+        try {
+            $legacyProxyAuthScheme = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/proxy_auth_scheme');
+            $obsoleteParameters[] = 'HTTP/proxy_auth_scheme';
+        } catch (\RuntimeException $e) {
+            $legacyProxyAuthScheme = '';
+        }
+
+        if ($legacyProxyHost !== '') {
+            $proxy = 'http://';
+            if ($legacyProxyAuthScheme !== '' && $legacyProxyUser !== '' && $legacyProxyPassword !== '') {
+                $proxy .= $legacyProxyUser . ':' . $legacyProxyPassword . '@';
+            }
+            $proxy .= $legacyProxyHost;
+            if ($legacyProxyPort !== '') {
+                $proxy .= ':' . $legacyProxyPort;
+            }
+            $newParameters['HTTP/proxy'] = $proxy;
+        }
+
+        // Verify peers
+        // see http://docs.guzzlephp.org/en/latest/request-options.html#verify
+        try {
+            $legacySslVerifyPeer = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/ssl_verify_peer');
+            $obsoleteParameters[] = 'HTTP/ssl_verify_peer';
+        } catch (\RuntimeException $e) {
+            $legacySslVerifyPeer = '';
         }
 
+        // Directory holding multiple Certificate Authority files
         try {
-            $curlUse = $this->configurationManager->getLocalConfigurationValueByPath('SYS/curlUse');
+            $legacySslCaPath = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/ssl_capath');
+            $obsoleteParameters[] = 'HTTP/ssl_capath';
         } catch (\RuntimeException $e) {
-            $curlUse = '';
+            $legacySslCaPath = '';
         }
+        // Certificate Authority file to verify the peer with (use when ssl_verify_peer is TRUE)
         try {
-            $adapter = $this->configurationManager->getConfigurationValueByPath('HTTP/adapter');
+            $legacySslCaFile = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/ssl_cafile');
+            $obsoleteParameters[] = 'HTTP/ssl_cafile';
         } catch (\RuntimeException $e) {
-            $adapter = '';
+            $legacySslCaFile = '';
+        }
+        if ($legacySslVerifyPeer !== '') {
+            if ($legacySslCaFile !== '' && $legacySslCaPath !== '') {
+                $newParameters['HTTP/verify'] = $legacySslCaPath . $legacySslCaFile;
+            } elseif ((bool)$legacySslVerifyPeer === false) {
+                $newParameters['HTTP/verify'] = false;
+            }
         }
-        if (!empty($curlUse) && $adapter !== 'curl') {
-            $GLOBALS['TYPO3_CONF_VARS']['HTTP']['adapter'] = 'curl';
-            $this->configurationManager->setLocalConfigurationValueByPath('HTTP/adapter', 'curl');
+
+        // SSL Key + Passphrase
+        // Name of a file containing local certificate
+        try {
+            $legacySslLocalCert = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/ssl_local_cert');
+            $obsoleteParameters[] = 'HTTP/ssl_local_cert';
+        } catch (\RuntimeException $e) {
+            $legacySslLocalCert = '';
+        }
+
+        // Passphrase with which local certificate was encoded
+        try {
+            $legacySslPassphrase = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/ssl_passphrase');
+            $obsoleteParameters[] = 'HTTP/ssl_passphrase';
+        } catch (\RuntimeException $e) {
+            $legacySslPassphrase = '';
+        }
+
+        if ($legacySslLocalCert !== '') {
+            if ($legacySslPassphrase !== '') {
+                $newParameters['HTTP/ssl_key'] = [
+                    $legacySslLocalCert,
+                    $legacySslPassphrase
+                ];
+            } else {
+                $newParameters['HTTP/ssl_key'] = $legacySslLocalCert;
+            }
+        }
+
+        // Update the LocalConfiguration file if obsolete parameters or new parameters are set
+        if (!empty($obsoleteParameters)) {
+            $this->configurationManager->removeLocalConfigurationKeysByPath($obsoleteParameters);
+            $changed = true;
+        }
+        if (!empty($newParameters)) {
+            $this->configurationManager->setLocalConfigurationValuesByPathValuePairs($newParameters);
             $changed = true;
         }
         if ($changed) {
@@ -496,7 +608,8 @@ class SilentConfigurationUpgradeService
      *
      * @return void
      */
-    protected function migrateThumbnailsPngSetting() {
+    protected function migrateThumbnailsPngSetting()
+    {
         $changedValues = array();
         try {
             $currentThumbnailsPngValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/thumbnails_png');
index 75cd002..c34e6ec 100644 (file)
@@ -151,7 +151,7 @@ abstract class AbstractDownloadExtensionUpdate extends AbstractUpdate
                 1344687436);
         }
 
-        $fileContent = GeneralUtility::getUrl($url, 0, array(TYPO3_user_agent));
+        $fileContent = GeneralUtility::getUrl($url);
 
         // Can not fetch url, throw an exception
         if ($fileContent === false) {
index 9483035..450d585 100644 (file)
@@ -325,114 +325,6 @@ class SilentConfigurationUpgradeServiceTest extends \TYPO3\CMS\Core\Tests\UnitTe
     /**
      * @test
      */
-    public function noProxyAuthSchemeSetInLocalConfiguration()
-    {
-        /** @var $silentConfigurationUpgradeServiceInstance SilentConfigurationUpgradeService|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface */
-        $silentConfigurationUpgradeServiceInstance = $this->getAccessibleMock(
-            SilentConfigurationUpgradeService::class,
-            array('dummy'),
-            array(),
-            '',
-            false
-        );
-
-        $closure = function () {
-            throw new \RuntimeException('Path does not exist in array', 1341397869);
-        };
-
-        $this->createConfigurationManagerWithMockedMethods(
-            array(
-                'getLocalConfigurationValueByPath',
-                'removeLocalConfigurationKeysByPath',
-            )
-        );
-        $this->configurationManager->expects($this->exactly(1))
-            ->method('getLocalConfigurationValueByPath')
-            ->will($this->returnCallback($closure));
-        $this->configurationManager->expects($this->never())
-            ->method('removeLocalConfigurationKeysByPath');
-
-        $silentConfigurationUpgradeServiceInstance->_set('configurationManager', $this->configurationManager);
-
-        $silentConfigurationUpgradeServiceInstance->_call('setProxyAuthScheme');
-    }
-
-    /**
-     * @test
-     */
-    public function proxyAuthSchemeIsDigest()
-    {
-        /** @var $silentConfigurationUpgradeServiceInstance SilentConfigurationUpgradeService|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface */
-        $silentConfigurationUpgradeServiceInstance = $this->getAccessibleMock(
-            SilentConfigurationUpgradeService::class,
-            array('dummy'),
-            array(),
-            '',
-            false
-        );
-
-        $currentLocalConfiguration = array(
-            array('HTTP/proxy_auth_scheme', 'digest')
-        );
-
-        $this->createConfigurationManagerWithMockedMethods(
-            array(
-                'getLocalConfigurationValueByPath',
-                'removeLocalConfigurationKeysByPath',
-            )
-        );
-        $this->configurationManager->expects($this->exactly(1))
-            ->method('getLocalConfigurationValueByPath')
-            ->will($this->returnValueMap($currentLocalConfiguration));
-        $this->configurationManager->expects($this->never())
-            ->method('removeLocalConfigurationKeysByPath');
-
-        $silentConfigurationUpgradeServiceInstance->_set('configurationManager', $this->configurationManager);
-
-        $silentConfigurationUpgradeServiceInstance->_call('setProxyAuthScheme');
-    }
-
-    /**
-     * @test
-     */
-    public function proxyAuthSchemeIsBasic()
-    {
-        /** @var $silentConfigurationUpgradeServiceInstance SilentConfigurationUpgradeService|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface */
-        $silentConfigurationUpgradeServiceInstance = $this->getAccessibleMock(
-            SilentConfigurationUpgradeService::class,
-            array('dummy'),
-            array(),
-            '',
-            false
-        );
-
-        $currentLocalConfiguration = array(
-            array('HTTP/proxy_auth_scheme', 'basic')
-        );
-
-        $this->createConfigurationManagerWithMockedMethods(
-            array(
-                'getLocalConfigurationValueByPath',
-                'removeLocalConfigurationKeysByPath',
-            )
-        );
-        $this->configurationManager->expects($this->exactly(1))
-            ->method('getLocalConfigurationValueByPath')
-            ->will($this->returnValueMap($currentLocalConfiguration));
-        $this->configurationManager->expects($this->once())
-            ->method('removeLocalConfigurationKeysByPath')
-            ->with($this->equalTo(array('HTTP/proxy_auth_scheme')));
-
-        $this->setExpectedException(RedirectException::class);
-
-        $silentConfigurationUpgradeServiceInstance->_set('configurationManager', $this->configurationManager);
-
-        $silentConfigurationUpgradeServiceInstance->_call('setProxyAuthScheme');
-    }
-
-    /**
-     * @test
-     */
     public function doNotGenerateEncryptionKeyIfExists()
     {
         /** @var $silentConfigurationUpgradeServiceInstance SilentConfigurationUpgradeService|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface */
@@ -504,28 +396,112 @@ class SilentConfigurationUpgradeServiceTest extends \TYPO3\CMS\Core\Tests\UnitTe
     }
 
     /**
-     * Dataprovider for transferDeprecatedCurlSettings
+     * Data provider for transferHttpSettings
      *
      * @return array
      */
-    public function curlProxySettingsToHttpSettingsMapping()
+    public function httpSettingsMappingDataProvider()
     {
-        return array(
-            array('http://proxy:3128/', 'proxy', '3128'),
-            array('http://proxy:3128', 'proxy', '3128'),
-            array('proxy:3128', 'proxy', '3128'),
-            array('https://proxy:3128/', 'proxy', '3128'),
-        );
+        return [
+            'No changes overridden in Local Configuration' => [
+                ['timeout' => 100],
+                ['HTTP/timeout' => 100],
+                false
+            ],
+            'Old and unused settings removed' => [
+                ['adapter' => 'curl'],
+                [],
+                true
+            ],
+            'Old and used settings changed' => [
+                ['protocol_version' => '1.1'],
+                ['HTTP/version' => '1.1'],
+                true
+            ],
+
+            /** redirect options */
+            'Redirects moved to default' => [
+                ['follow_redirects' => true],
+                [],
+                true
+            ],
+            'Redirects moved #1' => [
+                ['follow_redirects' => true, 'max_redirects' => 200, 'strict_redirects' => false],
+                ['HTTP/allow_redirects' => ['max' => 200]],
+                true
+            ],
+            'Redirects moved #2' => [
+                ['follow_redirects' => false, 'max_redirects' => 200, 'strict_redirects' => false],
+                ['HTTP/allow_redirects' => false],
+                true
+            ],
+            'Redirects moved #3' => [
+                ['follow_redirects' => true, 'max_redirects' => 400, 'strict_redirects' => 1],
+                ['HTTP/allow_redirects' => ['max' => 400, 'strict' => true]],
+                true
+            ],
+
+            /** Proxy settings */
+            'Proxy host set' => [
+                ['proxy_host' => 'vpn.myproxy.com'],
+                ['HTTP/proxy' => 'http://vpn.myproxy.com'],
+                true
+            ],
+            'Proxy host set + port' => [
+                ['proxy_host' => 'vpn.myproxy.com', 'proxy_port' => 8080],
+                ['HTTP/proxy' => 'http://vpn.myproxy.com:8080'],
+                true
+            ],
+            'Proxy host set + port + verification' => [
+                ['proxy_host' => 'vpn.myproxy.com', 'proxy_port' => 8080, 'proxy_auth_scheme' => 'basic', 'proxy_user' => 'myuser', 'proxy_password' => 'mysecret'],
+                ['HTTP/proxy' => 'http://myuser:mysecret@vpn.myproxy.com:8080'],
+                true
+            ],
+
+            /** SSL verification */
+            'Only ssl_capath set, invalid migration' => [
+                ['ssl_capath' => '/foo/bar/'],
+                [],
+                true
+            ],
+            'Verification activated, but only ssl_capath set, using default' => [
+                ['ssl_verify_peer' => 1, 'ssl_capath' => '/foo/bar/'],
+                [],
+                true
+            ],
+            'Verification activated, with ssl_capath and ssl_cafile set' => [
+                ['ssl_verify_peer' => 1, 'ssl_capath' => '/foo/bar/', 'ssl_cafile' => 'supersecret.crt'],
+                ['HTTP/verify' => '/foo/bar/supersecret.crt'],
+                true
+            ],
+
+            /** SSL key + passphrase */
+            'SSL key certification' => [
+                ['ssl_local_cert' => '/foo/bar/supersecret.key'],
+                ['HTTP/ssl_key' => '/foo/bar/supersecret.key'],
+                true
+            ],
+            'SSL key certification + passphrase' => [
+                ['ssl_local_cert' => '/foo/bar/supersecret.key', 'ssl_passphrase' => 'donotcopypasteme'],
+                ['HTTP/ssl_key' => ['/foo/bar/supersecret.key', 'donotcopypasteme']],
+                true
+            ],
+            'SSL key passphrase only - no migration' => [
+                ['ssl_passphrase' => 'donotcopypasteme'],
+                [],
+                true
+            ],
+        ];
     }
 
     /**
      * @test
-     * @dataProvider curlProxySettingsToHttpSettingsMapping
-     * @param string $curlProxyServer
-     * @param string $proxyHost
-     * @param string $proxyPort
+     * @dataProvider httpSettingsMappingDataProvider
+     * @param array $currentLocalConfiguration
+     * @param array $newSettings
+     * @param bool $localConfigurationNeedsUpdate
      */
-    public function transferDeprecatedCurlSettings($curlProxyServer, $proxyHost, $proxyPort)
+    public function transferHttpSettingsIfSet($currentLocalConfiguration, $newSettings, $localConfigurationNeedsUpdate)
     {
         /** @var $silentConfigurationUpgradeServiceInstance SilentConfigurationUpgradeService|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface */
         $silentConfigurationUpgradeServiceInstance = $this->getAccessibleMock(
@@ -536,120 +512,33 @@ class SilentConfigurationUpgradeServiceTest extends \TYPO3\CMS\Core\Tests\UnitTe
             false
         );
 
-        $currentLocalConfiguration = array(
-            array('SYS/curlProxyServer',  $curlProxyServer),
-            array('HTTP/proxy_host', ''),
-            array('SYS/curlProxyUserPass',  ''),
-            array('HTTP/proxy_user', ''),
-            array('SYS/curlUse', false)
-        );
-        $this->createConfigurationManagerWithMockedMethods(
-            array(
-                'getLocalConfigurationValueByPath',
-                'setLocalConfigurationValueByPath',
-                'getConfigurationValueByPath'
-            )
-        );
-        $this->configurationManager->expects($this->exactly(5))
-            ->method('getLocalConfigurationValueByPath')
-            ->will($this->returnValueMap($currentLocalConfiguration));
-        $this->configurationManager->expects($this->exactly(2))
-            ->method('setLocalConfigurationValueByPath')
-            ->withConsecutive(
-                array('HTTP/proxy_host', $proxyHost),
-                array('HTTP/proxy_port', $proxyPort)
-            );
-
-        $this->setExpectedException(RedirectException::class);
-
-        $silentConfigurationUpgradeServiceInstance->_set('configurationManager', $this->configurationManager);
-
-        $silentConfigurationUpgradeServiceInstance->_call('transferDeprecatedCurlSettings');
-    }
-
-    /**
-     * @test
-     */
-    public function curlProxyServerDoesNotOverwriteHttpSettings()
-    {
-        /** @var $silentConfigurationUpgradeServiceInstance SilentConfigurationUpgradeService|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface */
-        $silentConfigurationUpgradeServiceInstance = $this->getAccessibleMock(
-            SilentConfigurationUpgradeService::class,
-            array('dummy'),
-            array(),
-            '',
-            false
-        );
-
-        $currentLocalConfiguration = array(
-            array('SYS/curlProxyServer', 'http://proxyOld:3128/'),
-            array('SYS/curlProxyUserPass', 'userOld:passOld'),
-            array('HTTP/proxy_host', 'proxyNew'),
-            array('HTTP/proxy_port', '3128'),
-            array('HTTP/proxy_user', 'userNew'),
-            array('HTTP/proxy_pass', 'passNew'),
-            array('SYS/curlUse', false)
-        );
         $this->createConfigurationManagerWithMockedMethods(
             array(
-                'getLocalConfigurationValueByPath',
-                'setLocalConfigurationValueByPath',
-                'getConfigurationValueByPath'
+                'setLocalConfigurationValuesByPathValuePairs',
+                'removeLocalConfigurationKeysByPath',
+                'getLocalConfiguration'
             )
         );
-        $this->configurationManager->expects($this->exactly(5))
-            ->method('getLocalConfigurationValueByPath')
-            ->will($this->returnValueMap($currentLocalConfiguration));
-        $this->configurationManager->expects($this->never())
-            ->method('setLocalConfigurationValueByPath');
-
-        $silentConfigurationUpgradeServiceInstance->_set('configurationManager', $this->configurationManager);
-
-        $silentConfigurationUpgradeServiceInstance->_call('transferDeprecatedCurlSettings');
-    }
-
-    /**
-     * @test
-     */
-    public function curlAdapterUsedIfCurlUse()
-    {
-        /** @var $silentConfigurationUpgradeServiceInstance SilentConfigurationUpgradeService|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface */
-        $silentConfigurationUpgradeServiceInstance = $this->getAccessibleMock(
-            SilentConfigurationUpgradeService::class,
-            array('dummy'),
-            array(),
-            '',
-            false
-        );
 
-        $currentLocalConfiguration = array(
-            array('SYS/curlProxyServer', ''),
-            array('SYS/curlProxyUserPass', ''),
-            array('HTTP/proxy_host', 'proxyNew'),
-            array('HTTP/proxy_user', 'userNew'),
-            array('SYS/curlUse', true)
-        );
-        $this->createConfigurationManagerWithMockedMethods(
-            array(
-                'getLocalConfigurationValueByPath',
-                'getConfigurationValueByPath',
-                'setLocalConfigurationValueByPath',
-            )
-        );
-        $this->configurationManager->expects($this->exactly(5))
-            ->method('getLocalConfigurationValueByPath')
-            ->will($this->returnValueMap($currentLocalConfiguration));
-        $this->configurationManager->expects($this->once())
-            ->method('setLocalConfigurationValueByPath')
-            ->withConsecutive(
-                array('HTTP/adapter', 'curl')
-            );
+        $this->configurationManager->expects($this->any())
+            ->method('getLocalConfiguration')
+            ->willReturn(['HTTP' => $currentLocalConfiguration]);
+        if ($localConfigurationNeedsUpdate) {
+            if (!empty($newSettings)) {
+                $this->configurationManager->expects($this->once())
+                    ->method('setLocalConfigurationValuesByPathValuePairs')
+                    ->with($newSettings);
+            }
+            $this->configurationManager->expects($this->atMost(1))->method('removeLocalConfigurationKeysByPath');
+        }
 
-        $this->setExpectedException(RedirectException::class);
+        if ($localConfigurationNeedsUpdate) {
+            $this->setExpectedException(RedirectException::class);
+        }
 
         $silentConfigurationUpgradeServiceInstance->_set('configurationManager', $this->configurationManager);
 
-        $silentConfigurationUpgradeServiceInstance->_call('transferDeprecatedCurlSettings');
+        $silentConfigurationUpgradeServiceInstance->_call('transferHttpSettings');
     }
 
     /**
index 2fbd295..3b90d97 100644 (file)
@@ -40,7 +40,7 @@ class TerService extends TerUtility implements SingletonInterface
         $result = false;
         $extPath = GeneralUtility::strtolower($extensionKey);
         $mirrorUrl .= $extPath[0] . '/' . $extPath[1] . '/' . $extPath . '-l10n/' . $extPath . '-l10n.xml';
-        $remote = GeneralUtility::getURL($mirrorUrl, 0, array(TYPO3_user_agent));
+        $remote = GeneralUtility::getUrl($mirrorUrl);
         if ($remote !== false) {
             $parsed = $this->parseL10nXML($remote);
             $result = $parsed['languagePackIndex'];
@@ -190,7 +190,7 @@ class TerService extends TerUtility implements SingletonInterface
             // Nothing to do
         }
 
-        $l10nResponse = GeneralUtility::getURL($mirrorUrl . $packageUrl, 0, array(TYPO3_user_agent));
+        $l10nResponse = GeneralUtility::getUrl($mirrorUrl . $packageUrl);
         if ($l10nResponse === false) {
             throw new XmlParserException('Error: Translation could not be fetched.', 1345736785);
         } else {
index b0e2b84..3849d3e 100644 (file)
@@ -14,7 +14,8 @@ namespace TYPO3\CMS\Linkvalidator\Linktype;
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Core\Http\HttpRequest;
+use GuzzleHttp\Exception\TooManyRedirectsException;
+use TYPO3\CMS\Core\Http\RequestFactory;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
@@ -63,51 +64,37 @@ class ExternalLinktype extends AbstractLinktype
             }
             return $this->urlReports[$url];
         }
-        $config = array(
-            'follow_redirects' => true,
-            'strict_redirects' => true
+
+        $options = array(
+            'cookies' => true,
+            'allow_redirects' => ['strict' => true]
         );
-        /** @var $request HttpRequest */
-        $request = GeneralUtility::makeInstance(HttpRequest::class, $url, 'HEAD', $config);
-        // Observe cookies
-        $request->setCookieJar(true);
+
+        /** @var RequestFactory $requestFactory */
+        $requestFactory = GeneralUtility::makeInstance(RequestFactory::class);
         try {
-            /** @var $response \HTTP_Request2_Response */
-            $response = $request->send();
-            $status = isset($response) ? $response->getStatus() : 0;
+            $response = $requestFactory->request($url, 'HEAD', $options);
             // HEAD was not allowed or threw an error, now trying GET
-            if ($status >= 400) {
-                $request->setMethod('GET');
-                $request->setHeader('Range', 'bytes = 0 - 4048');
-                /** @var $response \HTTP_Request2_Response */
-                $response = $request->send();
+            if ($response->getStatusCode() >= 400) {
+                $options['headers']['Range'] = 'bytes = 0 - 4048';
+                $response = $requestFactory->request($url, 'GET', $options);
             }
+            if ($response->getStatusCode() >= 300) {
+                $isValidUrl = false;
+                $errorParams['errorType'] = $response->getStatusCode();
+                $errorParams['message'] = $response->getReasonPhrase();
+            }
+        } catch (TooManyRedirectsException $e) {
+            $lastRequest = $e->getRequest();
+            $response = $e->getResponse();
+            $errorParams['errorType'] = 'loop';
+            $errorParams['location'] = (string)$lastRequest->getUri();
+            $errorParams['errorCode'] = $response->getStatusCode();
         } catch (\Exception $e) {
             $isValidUrl = false;
-            // A redirect loop occurred
-            if ($e->getCode() === 40) {
-                $traceUrl = $request->getUrl()->getURL();
-                /** @var \HTTP_Request2_Response $event['data'] */
-                $event = $request->getLastEvent();
-                if ($event['data'] instanceof \HTTP_Request2_Response) {
-                    $traceCode = $event['data']->getStatus();
-                } else {
-                    $traceCode = 'loop';
-                }
-                $errorParams['errorType'] = 'loop';
-                $errorParams['location'] = $traceUrl;
-                $errorParams['errorCode'] = $traceCode;
-            } else {
-                $errorParams['errorType'] = 'exception';
-            }
+            $errorParams['errorType'] = 'exception';
             $errorParams['message'] = $e->getMessage();
         }
-        $status = isset($response) ? $response->getStatus() : 0;
-        if ($status >= 300) {
-            $isValidUrl = false;
-            $errorParams['errorType'] = $status;
-            $errorParams['message'] = $response->getReasonPhrase();
-        }
         if (!$isValidUrl) {
             $this->setErrorParams($errorParams);
         }