mirror of
https://github.com/getgrav/grav.git
synced 2025-10-26 07:56:07 +01:00
switch to Symfony HTTPClient (#2901)
This commit is contained in:
@@ -55,7 +55,8 @@
|
||||
"phive/twig-extensions-deferred": "^1.0",
|
||||
"willdurand/negotiation": "2.x-dev",
|
||||
"itsgoingd/clockwork": "@beta",
|
||||
"enshrined/svg-sanitize": "~0.1"
|
||||
"enshrined/svg-sanitize": "~0.1",
|
||||
"symfony/http-client": "^4.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/codeception": "^2.4",
|
||||
|
||||
84
composer.lock
generated
84
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "3deb19274879ec4b3566d70163f0e4f3",
|
||||
"content-hash": "993f670b144e15558dd801ffcc7dfc81",
|
||||
"packages": [
|
||||
{
|
||||
"name": "antoligy/dom-string-iterators",
|
||||
@@ -2178,6 +2178,88 @@
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2020-03-27T16:54:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-client",
|
||||
"version": "v4.4.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-client.git",
|
||||
"reference": "88d1745f4095727b8bf0574a0f414331f4ec229c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-client/zipball/88d1745f4095727b8bf0574a0f414331f4ec229c",
|
||||
"reference": "88d1745f4095727b8bf0574a0f414331f4ec229c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.1.3",
|
||||
"psr/log": "^1.0",
|
||||
"symfony/http-client-contracts": "^1.1.8|^2",
|
||||
"symfony/polyfill-php73": "^1.11",
|
||||
"symfony/service-contracts": "^1.0|^2"
|
||||
},
|
||||
"provide": {
|
||||
"php-http/async-client-implementation": "*",
|
||||
"php-http/client-implementation": "*",
|
||||
"psr/http-client-implementation": "1.0",
|
||||
"symfony/http-client-implementation": "1.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"guzzlehttp/promises": "^1.3.1",
|
||||
"nyholm/psr7": "^1.0",
|
||||
"php-http/httplug": "^1.0|^2.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"symfony/dependency-injection": "^4.3|^5.0",
|
||||
"symfony/http-kernel": "^4.4",
|
||||
"symfony/process": "^4.2|^5.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "4.4-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\HttpClient\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony HttpClient component",
|
||||
"homepage": "https://symfony.com",
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-04-12T16:14:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.15.0",
|
||||
|
||||
@@ -11,81 +11,35 @@ namespace Grav\Common\GPM;
|
||||
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\Grav;
|
||||
use Symfony\Component\HttpClient\CurlHttpClient;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use Symfony\Component\HttpClient\HttpClient;
|
||||
use Symfony\Component\HttpClient\HttpOptions;
|
||||
use Symfony\Component\HttpClient\NativeHttpClient;
|
||||
|
||||
class Response
|
||||
{
|
||||
/** @var callable The callback for the progress, either a function or callback in array notation */
|
||||
public static $callback = null;
|
||||
|
||||
/** @var string Which method to use for HTTP calls, can be 'curl', 'fopen' or 'auto'. Auto is default and fopen is the preferred method */
|
||||
private static $method = 'auto';
|
||||
|
||||
/** @var array Default parameters for `curl` and `fopen` */
|
||||
private static $defaults = [
|
||||
|
||||
'curl' => [
|
||||
CURLOPT_REFERER => 'Grav GPM',
|
||||
CURLOPT_USERAGENT => 'Grav GPM',
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_FAILONERROR => true,
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
CURLOPT_HEADER => false,
|
||||
//CURLOPT_SSL_VERIFYPEER => true, // this is set in the constructor since it's a setting
|
||||
/**
|
||||
* Example of callback parameters from within your own class
|
||||
*/
|
||||
//CURLOPT_NOPROGRESS => false,
|
||||
//CURLOPT_PROGRESSFUNCTION => [$this, 'progress']
|
||||
],
|
||||
'fopen' => [
|
||||
'method' => 'GET',
|
||||
'user_agent' => 'Grav GPM',
|
||||
'max_redirects' => 5,
|
||||
'follow_location' => 1,
|
||||
'timeout' => 15,
|
||||
/* // this is set in the constructor since it's a setting
|
||||
'ssl' => [
|
||||
'verify_peer' => true,
|
||||
'verify_peer_name' => true,
|
||||
],
|
||||
*/
|
||||
/**
|
||||
* Example of callback parameters from within your own class
|
||||
*/
|
||||
//'notification' => [$this, 'progress']
|
||||
]
|
||||
private static $headers = [
|
||||
'Referer' => 'Grav CMS',
|
||||
'User-Agent' => 'Grav CMS'
|
||||
];
|
||||
|
||||
/**
|
||||
* Sets the preferred method to use for making HTTP calls.
|
||||
*
|
||||
* @param string $method Default is `auto`
|
||||
* @return Response
|
||||
*/
|
||||
public static function setMethod($method = 'auto')
|
||||
{
|
||||
if (!\in_array($method, ['auto', 'curl', 'fopen'], true)) {
|
||||
$method = 'auto';
|
||||
}
|
||||
|
||||
self::$method = $method;
|
||||
|
||||
return new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a request to the URL by using the preferred method
|
||||
*
|
||||
* @param string $uri URL to call
|
||||
* @param array $options An array of parameters for both `curl` and `fopen`
|
||||
* @param callable|null $callback Either a function or callback in array notation
|
||||
* @param string $uri URL to call
|
||||
* @param array $overrides An array of parameters for both `curl` and `fopen`
|
||||
* @param callable|null $callback Either a function or callback in array notation
|
||||
* @return string The response of the request
|
||||
* @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface
|
||||
*/
|
||||
public static function get($uri = '', $options = [], $callback = null)
|
||||
public static function get($uri = '', $overrides = [], $callback = null)
|
||||
{
|
||||
if (!self::isCurlAvailable() && !self::isFopenAvailable()) {
|
||||
throw new \RuntimeException('Could not start an HTTP request. `allow_url_open` is disabled and `cURL` is not available');
|
||||
if (empty($uri)) {
|
||||
throw new TransportException('missing URI');
|
||||
}
|
||||
|
||||
// check if this function is available, if so use it to stop any timeouts
|
||||
@@ -97,95 +51,50 @@ class Response
|
||||
}
|
||||
|
||||
$config = Grav::instance()['config'];
|
||||
$overrides = [];
|
||||
$options = new HttpOptions();
|
||||
|
||||
// Override CA Bundle
|
||||
$caPathOrFile = \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath();
|
||||
if (is_dir($caPathOrFile) || (is_link($caPathOrFile) && is_dir(readlink($caPathOrFile)))) {
|
||||
$overrides['curl'][CURLOPT_CAPATH] = $caPathOrFile;
|
||||
$overrides['fopen']['ssl']['capath'] = $caPathOrFile;
|
||||
} else {
|
||||
$overrides['curl'][CURLOPT_CAINFO] = $caPathOrFile;
|
||||
$overrides['fopen']['ssl']['cafile'] = $caPathOrFile;
|
||||
// Set default Headers
|
||||
$options->setHeaders(self::$headers);
|
||||
|
||||
// Disable verify Peer if required
|
||||
$verify_peer = $config->get('system.gpm.verify_peer', true);
|
||||
if ($verify_peer !== true) {
|
||||
$options->verifyPeer($verify_peer);
|
||||
}
|
||||
|
||||
// SSL Verify Peer and Proxy Setting
|
||||
$settings = [
|
||||
'method' => $config->get('system.gpm.method', self::$method),
|
||||
'verify_peer' => $config->get('system.gpm.verify_peer', true),
|
||||
// `system.proxy_url` is for fallback
|
||||
// introduced with 1.1.0-beta.1 probably safe to remove at some point
|
||||
'proxy_url' => $config->get('system.gpm.proxy_url', $config->get('system.proxy_url', false)),
|
||||
];
|
||||
|
||||
if (!$settings['verify_peer']) {
|
||||
$overrides = array_replace_recursive([], $overrides, [
|
||||
'curl' => [
|
||||
CURLOPT_SSL_VERIFYPEER => $settings['verify_peer'],
|
||||
CURLOPT_SSL_VERIFYHOST => false
|
||||
],
|
||||
'fopen' => [
|
||||
'ssl' => [
|
||||
'verify_peer' => $settings['verify_peer'],
|
||||
'verify_peer_name' => $settings['verify_peer'],
|
||||
]
|
||||
]
|
||||
]);
|
||||
// Set proxy url if provided
|
||||
$proxy_url = $config->get('system.gpm.proxy_url', false);
|
||||
if ($proxy_url) {
|
||||
$options->setProxy($proxy_url);
|
||||
}
|
||||
|
||||
// Proxy Setting
|
||||
if ($settings['proxy_url']) {
|
||||
$proxy = parse_url($settings['proxy_url']);
|
||||
$fopen_proxy = ($proxy['scheme'] ?: 'http') . '://' . $proxy['host'] . (isset($proxy['port']) ? ':' . $proxy['port'] : '');
|
||||
|
||||
$overrides = array_replace_recursive([], $overrides, [
|
||||
'curl' => [
|
||||
CURLOPT_PROXY => $proxy['host'],
|
||||
CURLOPT_PROXYTYPE => 'HTTP'
|
||||
],
|
||||
'fopen' => [
|
||||
'proxy' => $fopen_proxy,
|
||||
'request_fulluri' => true
|
||||
]
|
||||
]);
|
||||
|
||||
if (isset($proxy['port'])) {
|
||||
$overrides['curl'][CURLOPT_PROXYPORT] = $proxy['port'];
|
||||
}
|
||||
|
||||
if (isset($proxy['user'], $proxy['pass'])) {
|
||||
$fopen_auth = $auth = base64_encode($proxy['user'] . ':' . $proxy['pass']);
|
||||
$overrides['curl'][CURLOPT_PROXYUSERPWD] = $proxy['user'] . ':' . $proxy['pass'];
|
||||
$overrides['fopen']['header'] = "Proxy-Authorization: Basic $fopen_auth";
|
||||
}
|
||||
// Use callback if provided
|
||||
if ($callback) {
|
||||
self::$callback = $callback;
|
||||
$options->setOnProgress(['Grav\Common\GPM\Response', 'progress']);
|
||||
}
|
||||
|
||||
$options = array_replace_recursive(self::$defaults, $options, $overrides);
|
||||
$method = 'get' . ucfirst(strtolower($settings['method']));
|
||||
$preferred_method = $config->get('system.gpm.method', 'auto');
|
||||
|
||||
self::$callback = $callback;
|
||||
return static::$method($uri, $options, $callback);
|
||||
$settings = array_merge_recursive($options->toArray(), $overrides);
|
||||
|
||||
switch ($preferred_method) {
|
||||
case 'curl':
|
||||
$client = new CurlHttpClient($settings);
|
||||
break;
|
||||
case 'fopen':
|
||||
case 'native':
|
||||
$client = new NativeHttpClient($settings);
|
||||
break;
|
||||
default:
|
||||
$client = HttpClient::create($settings);
|
||||
}
|
||||
|
||||
$response = $client->request('GET', $uri);
|
||||
|
||||
return $response->getContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if cURL is available
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isCurlAvailable()
|
||||
{
|
||||
return function_exists('curl_version');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the remote fopen request is enabled in PHP
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isFopenAvailable()
|
||||
{
|
||||
return preg_match('/1|yes|on|true/i', ini_get('allow_url_fopen'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this a remote file or not
|
||||
@@ -202,219 +111,23 @@ class Response
|
||||
* Progress normalized for cURL and Fopen
|
||||
* Accepts a variable length of arguments passed in by stream method
|
||||
*/
|
||||
public static function progress()
|
||||
public static function progress(int $bytes_transferred, int $filesize, array $info)
|
||||
{
|
||||
static $filesize = null;
|
||||
|
||||
$args = func_get_args();
|
||||
$isCurlResource = is_resource($args[0]) && get_resource_type($args[0]) === 'curl';
|
||||
|
||||
$notification_code = !$isCurlResource ? $args[0] : false;
|
||||
$bytes_transferred = $isCurlResource ? $args[2] : $args[4];
|
||||
|
||||
if ($isCurlResource) {
|
||||
$filesize = $args[1];
|
||||
} elseif ($notification_code == STREAM_NOTIFY_FILE_SIZE_IS) {
|
||||
$filesize = $args[5];
|
||||
}
|
||||
|
||||
if ($bytes_transferred > 0) {
|
||||
if ($notification_code == STREAM_NOTIFY_PROGRESS | STREAM_NOTIFY_COMPLETED || $isCurlResource) {
|
||||
$percent = $filesize <= 0 ? 0 : round(($bytes_transferred * 100) / $filesize, 1);
|
||||
$progress = [
|
||||
'code' => $notification_code,
|
||||
'filesize' => $filesize,
|
||||
'transferred' => $bytes_transferred,
|
||||
'percent' => $percent < 100 ? $percent : 100
|
||||
];
|
||||
$percent = $filesize <= 0 ? 0 : intval(($bytes_transferred * 100) / $filesize);
|
||||
|
||||
if (self::$callback !== null) {
|
||||
call_user_func(self::$callback, $progress);
|
||||
}
|
||||
$progress = [
|
||||
'code' => $info['http_code'],
|
||||
'filesize' => $filesize,
|
||||
'transferred' => $bytes_transferred,
|
||||
'percent' => $percent < 100 ? $percent : 100
|
||||
];
|
||||
|
||||
if (self::$callback !== null) {
|
||||
call_user_func(self::$callback, $progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically picks the preferred method
|
||||
*
|
||||
* @return string|null The response of the request
|
||||
*/
|
||||
private static function getAuto()
|
||||
{
|
||||
if (!ini_get('open_basedir') && self::isFopenAvailable()) {
|
||||
return self::getFopen(func_get_args());
|
||||
}
|
||||
|
||||
if (self::isCurlAvailable()) {
|
||||
return self::getCurl(func_get_args());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a HTTP request via fopen
|
||||
*
|
||||
* @return string The response of the request
|
||||
*/
|
||||
private static function getFopen()
|
||||
{
|
||||
if (\count($args = func_get_args()) === 1) {
|
||||
$args = $args[0];
|
||||
}
|
||||
|
||||
$uri = $args[0];
|
||||
$options = $args[1] ?? [];
|
||||
$callback = $args[2] ?? null;
|
||||
|
||||
if ($callback) {
|
||||
$options['fopen']['notification'] = ['self', 'progress'];
|
||||
}
|
||||
|
||||
if (isset($options['fopen']['ssl'])) {
|
||||
$ssl = $options['fopen']['ssl'];
|
||||
unset($options['fopen']['ssl']);
|
||||
|
||||
$stream = stream_context_create([
|
||||
'http' => $options['fopen'],
|
||||
'ssl' => $ssl
|
||||
], $options['fopen']);
|
||||
} else {
|
||||
$stream = stream_context_create(['http' => $options['fopen']], $options['fopen']);
|
||||
}
|
||||
|
||||
|
||||
$content = @file_get_contents($uri, false, $stream);
|
||||
|
||||
if ($content === false) {
|
||||
$code = null;
|
||||
// Function file_get_contents() creates local variable $http_response_header, check it
|
||||
if (isset($http_response_header)) {
|
||||
$code = explode(' ', $http_response_header[0] ?? '')[1] ?? null;
|
||||
}
|
||||
|
||||
switch ($code) {
|
||||
case '404':
|
||||
throw new \RuntimeException('Page not found');
|
||||
case '401':
|
||||
throw new \RuntimeException('Invalid LICENSE');
|
||||
default:
|
||||
throw new \RuntimeException("Error while trying to download (code: {$code}): {$uri}\n");
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a HTTP request via cURL
|
||||
*
|
||||
* @return string The response of the request
|
||||
*/
|
||||
private static function getCurl()
|
||||
{
|
||||
$args = func_get_args();
|
||||
$args = count($args) > 1 ? $args : array_shift($args);
|
||||
|
||||
$uri = $args[0];
|
||||
$options = $args[1] ?? [];
|
||||
$callback = $args[2] ?? null;
|
||||
|
||||
$ch = curl_init($uri);
|
||||
|
||||
$response = static::curlExecFollow($ch, $options, $callback);
|
||||
$errno = curl_errno($ch);
|
||||
|
||||
if ($errno) {
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$error_message = curl_strerror($errno) . "\n" . curl_error($ch);
|
||||
|
||||
switch ($code) {
|
||||
case '404':
|
||||
throw new \RuntimeException('Page not found');
|
||||
case '401':
|
||||
throw new \RuntimeException('Invalid LICENSE');
|
||||
default:
|
||||
throw new \RuntimeException("Error while trying to download (code: $code): $uri \nMessage: $error_message");
|
||||
}
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $ch
|
||||
* @param array $options
|
||||
* @param bool $callback
|
||||
* @return bool|mixed
|
||||
*/
|
||||
private static function curlExecFollow($ch, $options, $callback)
|
||||
{
|
||||
if ($callback) {
|
||||
curl_setopt_array(
|
||||
$ch,
|
||||
[
|
||||
CURLOPT_NOPROGRESS => false,
|
||||
CURLOPT_PROGRESSFUNCTION => ['self', 'progress']
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// no open_basedir set, we can proceed normally
|
||||
if (!ini_get('open_basedir')) {
|
||||
curl_setopt_array($ch, $options['curl']);
|
||||
return curl_exec($ch);
|
||||
}
|
||||
|
||||
$max_redirects = $options['curl'][CURLOPT_MAXREDIRS] ?? 5;
|
||||
$options['curl'][CURLOPT_FOLLOWLOCATION] = false;
|
||||
|
||||
// open_basedir set but no redirects to follow, we can disable followlocation and proceed normally
|
||||
curl_setopt_array($ch, $options['curl']);
|
||||
if ($max_redirects <= 0) {
|
||||
return curl_exec($ch);
|
||||
}
|
||||
|
||||
$uri = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
|
||||
$rch = curl_copy_handle($ch);
|
||||
|
||||
curl_setopt($rch, CURLOPT_HEADER, true);
|
||||
curl_setopt($rch, CURLOPT_NOBODY, true);
|
||||
curl_setopt($rch, CURLOPT_FORBID_REUSE, false);
|
||||
curl_setopt($rch, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
do {
|
||||
curl_setopt($rch, CURLOPT_URL, $uri);
|
||||
$header = curl_exec($rch);
|
||||
|
||||
if (curl_errno($rch)) {
|
||||
$code = 0;
|
||||
} else {
|
||||
$code = (int)curl_getinfo($rch, CURLINFO_HTTP_CODE);
|
||||
if ($code === 301 || $code === 302 || $code === 303) {
|
||||
preg_match('/Location:(.*?)\n/', $header, $matches);
|
||||
$uri = trim(array_pop($matches));
|
||||
} else {
|
||||
$code = 0;
|
||||
}
|
||||
}
|
||||
} while ($code && --$max_redirects);
|
||||
|
||||
curl_close($rch);
|
||||
|
||||
if (!$max_redirects) {
|
||||
if ($max_redirects === null) {
|
||||
trigger_error('Too many redirects. When following redirects, libcurl hit the maximum amount.', E_USER_WARNING);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
curl_setopt($ch, CURLOPT_URL, $uri);
|
||||
|
||||
return curl_exec($ch);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user