first commit
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
23c2469e2a568362a62eecf1b49ed90a15621e6fa30e29947ded3436422de9b9 cacert.pem
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace VendorDuplicator;
|
||||
|
||||
/**
|
||||
* Backwards compatibility layer for Requests.
|
||||
*
|
||||
* Allows for Composer to autoload the old PSR-0 classes via the custom autoloader.
|
||||
* This prevents issues with _extending final classes_ (which was the previous solution).
|
||||
*
|
||||
* Please see the Changelog for the 2.0.0 release for upgrade notes.
|
||||
*
|
||||
* @package Requests
|
||||
*
|
||||
* @deprecated 2.0.0 Use the PSR-4 class names instead.
|
||||
*/
|
||||
if (\class_exists('VendorDuplicator\\WpOrg\\Requests\\Autoload') === \false) {
|
||||
require_once \dirname(__DIR__) . '/src/Autoload.php';
|
||||
}
|
||||
WpOrg\Requests\Autoload::register();
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace VendorDuplicator;
|
||||
|
||||
/**
|
||||
* Requests for PHP
|
||||
*
|
||||
* Inspired by Requests for Python.
|
||||
*
|
||||
* Based on concepts from SimplePie_File, RequestCore and WP_Http.
|
||||
*
|
||||
* @package Requests
|
||||
*
|
||||
* @deprecated 2.0.0
|
||||
*/
|
||||
/*
|
||||
* Integrators who cannot yet upgrade to the PSR-4 class names can silence deprecations
|
||||
* by defining a `REQUESTS_SILENCE_PSR0_DEPRECATIONS` constant and setting it to `true`.
|
||||
* The constant needs to be defined before this class is required.
|
||||
*/
|
||||
if (!\defined('REQUESTS_SILENCE_PSR0_DEPRECATIONS') || \REQUESTS_SILENCE_PSR0_DEPRECATIONS !== \true) {
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
|
||||
\trigger_error('The PSR-0 `Requests_...` class names in the Requests library are deprecated.' . ' Switch to the PSR-4 `WpOrg\\Requests\\...` class names at your earliest convenience.', \E_USER_DEPRECATED);
|
||||
// Prevent the deprecation notice from being thrown twice.
|
||||
if (!\defined('REQUESTS_SILENCE_PSR0_DEPRECATIONS')) {
|
||||
\define('REQUESTS_SILENCE_PSR0_DEPRECATIONS', \true);
|
||||
}
|
||||
}
|
||||
require_once \dirname(__DIR__) . '/src/Requests.php';
|
||||
/**
|
||||
* Requests for PHP
|
||||
*
|
||||
* Inspired by Requests for Python.
|
||||
*
|
||||
* Based on concepts from SimplePie_File, RequestCore and WP_Http.
|
||||
*
|
||||
* @package Requests
|
||||
*
|
||||
* @deprecated 2.0.0 Use `WpOrg\Requests\Requests` instead for the actual functionality and
|
||||
* use `WpOrg\Requests\Autoload` for the autoloading.
|
||||
*/
|
||||
class Requests extends WpOrg\Requests\Requests
|
||||
{
|
||||
/**
|
||||
* Deprecated autoloader for Requests.
|
||||
*
|
||||
* @deprecated 2.0.0 Use the `WpOrg\Requests\Autoload::load()` method instead.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $class Class name to load
|
||||
*/
|
||||
public static function autoloader($class)
|
||||
{
|
||||
if (\class_exists('VendorDuplicator\\WpOrg\\Requests\\Autoload') === \false) {
|
||||
require_once \dirname(__DIR__) . '/src/Autoload.php';
|
||||
}
|
||||
return WpOrg\Requests\Autoload::load($class);
|
||||
}
|
||||
/**
|
||||
* Register the built-in autoloader
|
||||
*
|
||||
* @deprecated 2.0.0 Include the `WpOrg\Requests\Autoload` class and
|
||||
* call `WpOrg\Requests\Autoload::register()` instead.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function register_autoloader()
|
||||
{
|
||||
require_once \dirname(__DIR__) . '/src/Autoload.php';
|
||||
WpOrg\Requests\Autoload::register();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Requests for PHP
|
||||
*
|
||||
* Inspired by Requests for Python.
|
||||
*
|
||||
* Based on concepts from SimplePie_File, RequestCore and WP_Http.
|
||||
*
|
||||
* @package Requests
|
||||
*
|
||||
* @deprecated 2.0.0 Use `WpOrg\Requests\Requests` instead for the actual functionality and
|
||||
* use `WpOrg\Requests\Autoload` for the autoloading.
|
||||
*/
|
||||
\class_alias('VendorDuplicator\\Requests', 'Requests', \false);
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Authentication provider interface
|
||||
*
|
||||
* @package Requests\Authentication
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Hooks;
|
||||
/**
|
||||
* Authentication provider interface
|
||||
*
|
||||
* Implement this interface to act as an authentication provider.
|
||||
*
|
||||
* Parameters should be passed via the constructor where possible, as this
|
||||
* makes it much easier for users to use your provider.
|
||||
*
|
||||
* @see \VendorDuplicator\WpOrg\Requests\Hooks
|
||||
*
|
||||
* @package Requests\Authentication
|
||||
*/
|
||||
interface Auth
|
||||
{
|
||||
/**
|
||||
* Register hooks as needed
|
||||
*
|
||||
* This method is called in {@see \WpOrg\Requests\Requests::request()} when the user
|
||||
* has set an instance as the 'auth' option. Use this callback to register all the
|
||||
* hooks you'll need.
|
||||
*
|
||||
* @see \VendorDuplicator\WpOrg\Requests\Hooks::register()
|
||||
* @param \VendorDuplicator\WpOrg\Requests\Hooks $hooks Hook system
|
||||
*/
|
||||
public function register(Hooks $hooks);
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Basic Authentication provider
|
||||
*
|
||||
* @package Requests\Authentication
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Auth;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Auth;
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\ArgumentCount;
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument;
|
||||
use VendorDuplicator\WpOrg\Requests\Hooks;
|
||||
/**
|
||||
* Basic Authentication provider
|
||||
*
|
||||
* Provides a handler for Basic HTTP authentication via the Authorization
|
||||
* header.
|
||||
*
|
||||
* @package Requests\Authentication
|
||||
*/
|
||||
class Basic implements Auth
|
||||
{
|
||||
/**
|
||||
* Username
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $user;
|
||||
/**
|
||||
* Password
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $pass;
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @since 2.0 Throws an `InvalidArgument` exception.
|
||||
* @since 2.0 Throws an `ArgumentCount` exception instead of the Requests base `Exception.
|
||||
*
|
||||
* @param array|null $args Array of user and password. Must have exactly two elements
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed argument is not an array or null.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\ArgumentCount On incorrect number of array elements (`authbasicbadargs`).
|
||||
*/
|
||||
public function __construct($args = null)
|
||||
{
|
||||
if (\is_array($args)) {
|
||||
if (\count($args) !== 2) {
|
||||
throw ArgumentCount::create('an array with exactly two elements', \count($args), 'authbasicbadargs');
|
||||
}
|
||||
list($this->user, $this->pass) = $args;
|
||||
return;
|
||||
}
|
||||
if ($args !== null) {
|
||||
throw InvalidArgument::create(1, '$args', 'array|null', \gettype($args));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Register the necessary callbacks
|
||||
*
|
||||
* @see \VendorDuplicator\WpOrg\Requests\Auth\Basic::curl_before_send()
|
||||
* @see \VendorDuplicator\WpOrg\Requests\Auth\Basic::fsockopen_header()
|
||||
* @param \VendorDuplicator\WpOrg\Requests\Hooks $hooks Hook system
|
||||
*/
|
||||
public function register(Hooks $hooks)
|
||||
{
|
||||
$hooks->register('curl.before_send', [$this, 'curl_before_send']);
|
||||
$hooks->register('fsockopen.after_headers', [$this, 'fsockopen_header']);
|
||||
}
|
||||
/**
|
||||
* Set cURL parameters before the data is sent
|
||||
*
|
||||
* @param resource|\CurlHandle $handle cURL handle
|
||||
*/
|
||||
public function curl_before_send(&$handle)
|
||||
{
|
||||
\curl_setopt($handle, \CURLOPT_HTTPAUTH, \CURLAUTH_BASIC);
|
||||
\curl_setopt($handle, \CURLOPT_USERPWD, $this->getAuthString());
|
||||
}
|
||||
/**
|
||||
* Add extra headers to the request before sending
|
||||
*
|
||||
* @param string $out HTTP header string
|
||||
*/
|
||||
public function fsockopen_header(&$out)
|
||||
{
|
||||
$out .= \sprintf("Authorization: Basic %s\r\n", \base64_encode($this->getAuthString()));
|
||||
}
|
||||
/**
|
||||
* Get the authentication string (user:pass)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAuthString()
|
||||
{
|
||||
return $this->user . ':' . $this->pass;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Autoloader for Requests for PHP.
|
||||
*
|
||||
* Include this file if you'd like to avoid having to create your own autoloader.
|
||||
*
|
||||
* @package Requests
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests;
|
||||
|
||||
/*
|
||||
* Ensure the autoloader is only declared once.
|
||||
* This safeguard is in place as this is the typical entry point for this library
|
||||
* and this file being required unconditionally could easily cause
|
||||
* fatal "Class already declared" errors.
|
||||
*/
|
||||
if (\class_exists('VendorDuplicator\\WpOrg\\Requests\\Autoload') === \false) {
|
||||
/**
|
||||
* Autoloader for Requests for PHP.
|
||||
*
|
||||
* This autoloader supports the PSR-4 based Requests 2.0.0 classes in a case-sensitive manner
|
||||
* as the most common server OS-es are case-sensitive and the file names are in mixed case.
|
||||
*
|
||||
* For the PSR-0 Requests 1.x BC-layer, requested classes will be treated case-insensitively.
|
||||
*
|
||||
* @package Requests
|
||||
*/
|
||||
final class Autoload
|
||||
{
|
||||
/**
|
||||
* List of the old PSR-0 class names in lowercase as keys with their PSR-4 case-sensitive name as a value.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $deprecated_classes = [
|
||||
// Interfaces.
|
||||
'requests_auth' => 'VendorDuplicator\\WpOrg\\Requests\\Auth',
|
||||
'requests_hooker' => 'VendorDuplicator\\WpOrg\\Requests\\HookManager',
|
||||
'requests_proxy' => 'VendorDuplicator\\WpOrg\\Requests\\Proxy',
|
||||
'requests_transport' => 'VendorDuplicator\\WpOrg\\Requests\\Transport',
|
||||
// Classes.
|
||||
'requests_cookie' => 'VendorDuplicator\\WpOrg\\Requests\\Cookie',
|
||||
'requests_exception' => 'VendorDuplicator\\WpOrg\\Requests\\Exception',
|
||||
'requests_hooks' => 'VendorDuplicator\\WpOrg\\Requests\\Hooks',
|
||||
'requests_idnaencoder' => 'VendorDuplicator\\WpOrg\\Requests\\IdnaEncoder',
|
||||
'requests_ipv6' => 'VendorDuplicator\\WpOrg\\Requests\\Ipv6',
|
||||
'requests_iri' => 'VendorDuplicator\\WpOrg\\Requests\\Iri',
|
||||
'requests_response' => 'VendorDuplicator\\WpOrg\\Requests\\Response',
|
||||
'requests_session' => 'VendorDuplicator\\WpOrg\\Requests\\Session',
|
||||
'requests_ssl' => 'VendorDuplicator\\WpOrg\\Requests\\Ssl',
|
||||
'requests_auth_basic' => 'VendorDuplicator\\WpOrg\\Requests\\Auth\\Basic',
|
||||
'requests_cookie_jar' => 'VendorDuplicator\\WpOrg\\Requests\\Cookie\\Jar',
|
||||
'requests_proxy_http' => 'VendorDuplicator\\WpOrg\\Requests\\Proxy\\Http',
|
||||
'requests_response_headers' => 'VendorDuplicator\\WpOrg\\Requests\\Response\\Headers',
|
||||
'requests_transport_curl' => 'VendorDuplicator\\WpOrg\\Requests\\Transport\\Curl',
|
||||
'requests_transport_fsockopen' => 'VendorDuplicator\\WpOrg\\Requests\\Transport\\Fsockopen',
|
||||
'requests_utility_caseinsensitivedictionary' => 'VendorDuplicator\\WpOrg\\Requests\\Utility\\CaseInsensitiveDictionary',
|
||||
'requests_utility_filterediterator' => 'VendorDuplicator\\WpOrg\\Requests\\Utility\\FilteredIterator',
|
||||
'requests_exception_http' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http',
|
||||
'requests_exception_transport' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Transport',
|
||||
'requests_exception_transport_curl' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Transport\\Curl',
|
||||
'requests_exception_http_304' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status304',
|
||||
'requests_exception_http_305' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status305',
|
||||
'requests_exception_http_306' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status306',
|
||||
'requests_exception_http_400' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status400',
|
||||
'requests_exception_http_401' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status401',
|
||||
'requests_exception_http_402' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status402',
|
||||
'requests_exception_http_403' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status403',
|
||||
'requests_exception_http_404' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status404',
|
||||
'requests_exception_http_405' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status405',
|
||||
'requests_exception_http_406' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status406',
|
||||
'requests_exception_http_407' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status407',
|
||||
'requests_exception_http_408' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status408',
|
||||
'requests_exception_http_409' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status409',
|
||||
'requests_exception_http_410' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status410',
|
||||
'requests_exception_http_411' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status411',
|
||||
'requests_exception_http_412' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status412',
|
||||
'requests_exception_http_413' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status413',
|
||||
'requests_exception_http_414' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status414',
|
||||
'requests_exception_http_415' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status415',
|
||||
'requests_exception_http_416' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status416',
|
||||
'requests_exception_http_417' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status417',
|
||||
'requests_exception_http_418' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status418',
|
||||
'requests_exception_http_428' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status428',
|
||||
'requests_exception_http_429' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status429',
|
||||
'requests_exception_http_431' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status431',
|
||||
'requests_exception_http_500' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status500',
|
||||
'requests_exception_http_501' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status501',
|
||||
'requests_exception_http_502' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status502',
|
||||
'requests_exception_http_503' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status503',
|
||||
'requests_exception_http_504' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status504',
|
||||
'requests_exception_http_505' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status505',
|
||||
'requests_exception_http_511' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\Status511',
|
||||
'requests_exception_http_unknown' => 'VendorDuplicator\\WpOrg\\Requests\\Exception\\Http\\StatusUnknown',
|
||||
];
|
||||
/**
|
||||
* Register the autoloader.
|
||||
*
|
||||
* Note: the autoloader is *prepended* in the autoload queue.
|
||||
* This is done to ensure that the Requests 2.0 autoloader takes precedence
|
||||
* over a potentially (dependency-registered) Requests 1.x autoloader.
|
||||
*
|
||||
* @internal This method contains a safeguard against the autoloader being
|
||||
* registered multiple times. This safeguard uses a global constant to
|
||||
* (hopefully/in most cases) still function correctly, even if the
|
||||
* class would be renamed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register()
|
||||
{
|
||||
if (\defined('REQUESTS_AUTOLOAD_REGISTERED') === \false) {
|
||||
\spl_autoload_register([self::class, 'load'], \true);
|
||||
\define('REQUESTS_AUTOLOAD_REGISTERED', \true);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Autoloader.
|
||||
*
|
||||
* @param string $class_name Name of the class name to load.
|
||||
*
|
||||
* @return bool Whether a class was loaded or not.
|
||||
*/
|
||||
public static function load($class_name)
|
||||
{
|
||||
// Check that the class starts with "Requests" (PSR-0) or "VendorDuplicator\\WpOrg\Requests" (PSR-4).
|
||||
$psr_4_prefix_pos = \strpos($class_name, 'WpOrg\\Requests\\');
|
||||
if (\stripos($class_name, 'Requests') !== 0 && $psr_4_prefix_pos !== 0) {
|
||||
return \false;
|
||||
}
|
||||
$class_lower = \strtolower($class_name);
|
||||
if ($class_lower === 'requests') {
|
||||
// Reference to the original PSR-0 Requests class.
|
||||
$file = \dirname(__DIR__) . '/library/Requests.php';
|
||||
} elseif ($psr_4_prefix_pos === 0) {
|
||||
// PSR-4 classname.
|
||||
$file = __DIR__ . '/' . \strtr(\substr($class_name, 15), '\\', '/') . '.php';
|
||||
}
|
||||
if (isset($file) && \file_exists($file)) {
|
||||
include $file;
|
||||
return \true;
|
||||
}
|
||||
/*
|
||||
* Okay, so the class starts with "Requests", but we couldn't find the file.
|
||||
* If this is one of the deprecated/renamed PSR-0 classes being requested,
|
||||
* let's alias it to the new name and throw a deprecation notice.
|
||||
*/
|
||||
if (isset(self::$deprecated_classes[$class_lower])) {
|
||||
/*
|
||||
* Integrators who cannot yet upgrade to the PSR-4 class names can silence deprecations
|
||||
* by defining a `REQUESTS_SILENCE_PSR0_DEPRECATIONS` constant and setting it to `true`.
|
||||
* The constant needs to be defined before the first deprecated class is requested
|
||||
* via this autoloader.
|
||||
*/
|
||||
if (!\defined('REQUESTS_SILENCE_PSR0_DEPRECATIONS') || REQUESTS_SILENCE_PSR0_DEPRECATIONS !== \true) {
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
|
||||
\trigger_error('The PSR-0 `Requests_...` class names in the Requests library are deprecated.' . ' Switch to the PSR-4 `WpOrg\\Requests\\...` class names at your earliest convenience.', \E_USER_DEPRECATED);
|
||||
// Prevent the deprecation notice from being thrown twice.
|
||||
if (!\defined('REQUESTS_SILENCE_PSR0_DEPRECATIONS')) {
|
||||
\define('REQUESTS_SILENCE_PSR0_DEPRECATIONS', \true);
|
||||
}
|
||||
}
|
||||
// Create an alias and let the autoloader recursively kick in to load the PSR-4 class.
|
||||
return \class_alias(self::$deprecated_classes[$class_lower], $class_name, \true);
|
||||
}
|
||||
return \false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Capability interface declaring the known capabilities.
|
||||
*
|
||||
* @package Requests\Utilities
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests;
|
||||
|
||||
/**
|
||||
* Capability interface declaring the known capabilities.
|
||||
*
|
||||
* This is used as the authoritative source for which capabilities can be queried.
|
||||
*
|
||||
* @package Requests\Utilities
|
||||
*/
|
||||
interface Capability
|
||||
{
|
||||
/**
|
||||
* Support for SSL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SSL = 'ssl';
|
||||
/**
|
||||
* Collection of all capabilities supported in Requests.
|
||||
*
|
||||
* Note: this does not automatically mean that the capability will be supported for your chosen transport!
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
const ALL = [self::SSL];
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Cookie storage object
|
||||
*
|
||||
* @package Requests\Cookies
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument;
|
||||
use VendorDuplicator\WpOrg\Requests\Iri;
|
||||
use VendorDuplicator\WpOrg\Requests\Response\Headers;
|
||||
use VendorDuplicator\WpOrg\Requests\Utility\CaseInsensitiveDictionary;
|
||||
use VendorDuplicator\WpOrg\Requests\Utility\InputValidator;
|
||||
/**
|
||||
* Cookie storage object
|
||||
*
|
||||
* @package Requests\Cookies
|
||||
*/
|
||||
class Cookie
|
||||
{
|
||||
/**
|
||||
* Cookie name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
/**
|
||||
* Cookie value.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $value;
|
||||
/**
|
||||
* Cookie attributes
|
||||
*
|
||||
* Valid keys are `'path'`, `'domain'`, `'expires'`, `'max-age'`, `'secure'` and
|
||||
* `'httponly'`.
|
||||
*
|
||||
* @var \VendorDuplicator\WpOrg\Requests\Utility\CaseInsensitiveDictionary|array Array-like object
|
||||
*/
|
||||
public $attributes = [];
|
||||
/**
|
||||
* Cookie flags
|
||||
*
|
||||
* Valid keys are `'creation'`, `'last-access'`, `'persistent'` and `'host-only'`.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $flags = [];
|
||||
/**
|
||||
* Reference time for relative calculations
|
||||
*
|
||||
* This is used in place of `time()` when calculating Max-Age expiration and
|
||||
* checking time validity.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $reference_time = 0;
|
||||
/**
|
||||
* Create a new cookie object
|
||||
*
|
||||
* @param string $name The name of the cookie.
|
||||
* @param string $value The value for the cookie.
|
||||
* @param array|\WpOrg\Requests\Utility\CaseInsensitiveDictionary $attributes Associative array of attribute data
|
||||
* @param array $flags The flags for the cookie.
|
||||
* Valid keys are `'creation'`, `'last-access'`,
|
||||
* `'persistent'` and `'host-only'`.
|
||||
* @param int|null $reference_time Reference time for relative calculations.
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $name argument is not a string.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $value argument is not a string.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $attributes argument is not an array or iterable object with array access.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $flags argument is not an array.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $reference_time argument is not an integer or null.
|
||||
*/
|
||||
public function __construct($name, $value, $attributes = [], $flags = [], $reference_time = null)
|
||||
{
|
||||
if (\is_string($name) === \false) {
|
||||
throw InvalidArgument::create(1, '$name', 'string', \gettype($name));
|
||||
}
|
||||
if (\is_string($value) === \false) {
|
||||
throw InvalidArgument::create(2, '$value', 'string', \gettype($value));
|
||||
}
|
||||
if (InputValidator::has_array_access($attributes) === \false || InputValidator::is_iterable($attributes) === \false) {
|
||||
throw InvalidArgument::create(3, '$attributes', 'array|ArrayAccess&Traversable', \gettype($attributes));
|
||||
}
|
||||
if (\is_array($flags) === \false) {
|
||||
throw InvalidArgument::create(4, '$flags', 'array', \gettype($flags));
|
||||
}
|
||||
if ($reference_time !== null && \is_int($reference_time) === \false) {
|
||||
throw InvalidArgument::create(5, '$reference_time', 'integer|null', \gettype($reference_time));
|
||||
}
|
||||
$this->name = $name;
|
||||
$this->value = $value;
|
||||
$this->attributes = $attributes;
|
||||
$default_flags = ['creation' => \time(), 'last-access' => \time(), 'persistent' => \false, 'host-only' => \true];
|
||||
$this->flags = \array_merge($default_flags, $flags);
|
||||
$this->reference_time = \time();
|
||||
if ($reference_time !== null) {
|
||||
$this->reference_time = $reference_time;
|
||||
}
|
||||
$this->normalize();
|
||||
}
|
||||
/**
|
||||
* Get the cookie value
|
||||
*
|
||||
* Attributes and other data can be accessed via methods.
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
/**
|
||||
* Check if a cookie is expired.
|
||||
*
|
||||
* Checks the age against $this->reference_time to determine if the cookie
|
||||
* is expired.
|
||||
*
|
||||
* @return boolean True if expired, false if time is valid.
|
||||
*/
|
||||
public function is_expired()
|
||||
{
|
||||
// RFC6265, s. 4.1.2.2:
|
||||
// If a cookie has both the Max-Age and the Expires attribute, the Max-
|
||||
// Age attribute has precedence and controls the expiration date of the
|
||||
// cookie.
|
||||
if (isset($this->attributes['max-age'])) {
|
||||
$max_age = $this->attributes['max-age'];
|
||||
return $max_age < $this->reference_time;
|
||||
}
|
||||
if (isset($this->attributes['expires'])) {
|
||||
$expires = $this->attributes['expires'];
|
||||
return $expires < $this->reference_time;
|
||||
}
|
||||
return \false;
|
||||
}
|
||||
/**
|
||||
* Check if a cookie is valid for a given URI
|
||||
*
|
||||
* @param \VendorDuplicator\WpOrg\Requests\Iri $uri URI to check
|
||||
* @return boolean Whether the cookie is valid for the given URI
|
||||
*/
|
||||
public function uri_matches(Iri $uri)
|
||||
{
|
||||
if (!$this->domain_matches($uri->host)) {
|
||||
return \false;
|
||||
}
|
||||
if (!$this->path_matches($uri->path)) {
|
||||
return \false;
|
||||
}
|
||||
return empty($this->attributes['secure']) || $uri->scheme === 'https';
|
||||
}
|
||||
/**
|
||||
* Check if a cookie is valid for a given domain
|
||||
*
|
||||
* @param string $domain Domain to check
|
||||
* @return boolean Whether the cookie is valid for the given domain
|
||||
*/
|
||||
public function domain_matches($domain)
|
||||
{
|
||||
if (\is_string($domain) === \false) {
|
||||
return \false;
|
||||
}
|
||||
if (!isset($this->attributes['domain'])) {
|
||||
// Cookies created manually; cookies created by Requests will set
|
||||
// the domain to the requested domain
|
||||
return \true;
|
||||
}
|
||||
$cookie_domain = $this->attributes['domain'];
|
||||
if ($cookie_domain === $domain) {
|
||||
// The cookie domain and the passed domain are identical.
|
||||
return \true;
|
||||
}
|
||||
// If the cookie is marked as host-only and we don't have an exact
|
||||
// match, reject the cookie
|
||||
if ($this->flags['host-only'] === \true) {
|
||||
return \false;
|
||||
}
|
||||
if (\strlen($domain) <= \strlen($cookie_domain)) {
|
||||
// For obvious reasons, the cookie domain cannot be a suffix if the passed domain
|
||||
// is shorter than the cookie domain
|
||||
return \false;
|
||||
}
|
||||
if (\substr($domain, -1 * \strlen($cookie_domain)) !== $cookie_domain) {
|
||||
// The cookie domain should be a suffix of the passed domain.
|
||||
return \false;
|
||||
}
|
||||
$prefix = \substr($domain, 0, \strlen($domain) - \strlen($cookie_domain));
|
||||
if (\substr($prefix, -1) !== '.') {
|
||||
// The last character of the passed domain that is not included in the
|
||||
// domain string should be a %x2E (".") character.
|
||||
return \false;
|
||||
}
|
||||
// The passed domain should be a host name (i.e., not an IP address).
|
||||
return !\preg_match('#^(.+\\.)\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$#', $domain);
|
||||
}
|
||||
/**
|
||||
* Check if a cookie is valid for a given path
|
||||
*
|
||||
* From the path-match check in RFC 6265 section 5.1.4
|
||||
*
|
||||
* @param string $request_path Path to check
|
||||
* @return boolean Whether the cookie is valid for the given path
|
||||
*/
|
||||
public function path_matches($request_path)
|
||||
{
|
||||
if (empty($request_path)) {
|
||||
// Normalize empty path to root
|
||||
$request_path = '/';
|
||||
}
|
||||
if (!isset($this->attributes['path'])) {
|
||||
// Cookies created manually; cookies created by Requests will set
|
||||
// the path to the requested path
|
||||
return \true;
|
||||
}
|
||||
if (\is_scalar($request_path) === \false) {
|
||||
return \false;
|
||||
}
|
||||
$cookie_path = $this->attributes['path'];
|
||||
if ($cookie_path === $request_path) {
|
||||
// The cookie-path and the request-path are identical.
|
||||
return \true;
|
||||
}
|
||||
if (\strlen($request_path) > \strlen($cookie_path) && \substr($request_path, 0, \strlen($cookie_path)) === $cookie_path) {
|
||||
if (\substr($cookie_path, -1) === '/') {
|
||||
// The cookie-path is a prefix of the request-path, and the last
|
||||
// character of the cookie-path is %x2F ("/").
|
||||
return \true;
|
||||
}
|
||||
if (\substr($request_path, \strlen($cookie_path), 1) === '/') {
|
||||
// The cookie-path is a prefix of the request-path, and the
|
||||
// first character of the request-path that is not included in
|
||||
// the cookie-path is a %x2F ("/") character.
|
||||
return \true;
|
||||
}
|
||||
}
|
||||
return \false;
|
||||
}
|
||||
/**
|
||||
* Normalize cookie and attributes
|
||||
*
|
||||
* @return boolean Whether the cookie was successfully normalized
|
||||
*/
|
||||
public function normalize()
|
||||
{
|
||||
foreach ($this->attributes as $key => $value) {
|
||||
$orig_value = $value;
|
||||
if (\is_string($key)) {
|
||||
$value = $this->normalize_attribute($key, $value);
|
||||
}
|
||||
if ($value === null) {
|
||||
unset($this->attributes[$key]);
|
||||
continue;
|
||||
}
|
||||
if ($value !== $orig_value) {
|
||||
$this->attributes[$key] = $value;
|
||||
}
|
||||
}
|
||||
return \true;
|
||||
}
|
||||
/**
|
||||
* Parse an individual cookie attribute
|
||||
*
|
||||
* Handles parsing individual attributes from the cookie values.
|
||||
*
|
||||
* @param string $name Attribute name
|
||||
* @param string|int|bool $value Attribute value (string/integer value, or true if empty/flag)
|
||||
* @return mixed Value if available, or null if the attribute value is invalid (and should be skipped)
|
||||
*/
|
||||
protected function normalize_attribute($name, $value)
|
||||
{
|
||||
switch (\strtolower($name)) {
|
||||
case 'expires':
|
||||
// Expiration parsing, as per RFC 6265 section 5.2.1
|
||||
if (\is_int($value)) {
|
||||
return $value;
|
||||
}
|
||||
$expiry_time = \strtotime($value);
|
||||
if ($expiry_time === \false) {
|
||||
return null;
|
||||
}
|
||||
return $expiry_time;
|
||||
case 'max-age':
|
||||
// Expiration parsing, as per RFC 6265 section 5.2.2
|
||||
if (\is_int($value)) {
|
||||
return $value;
|
||||
}
|
||||
// Check that we have a valid age
|
||||
if (!\preg_match('/^-?\\d+$/', $value)) {
|
||||
return null;
|
||||
}
|
||||
$delta_seconds = (int) $value;
|
||||
if ($delta_seconds <= 0) {
|
||||
$expiry_time = 0;
|
||||
} else {
|
||||
$expiry_time = $this->reference_time + $delta_seconds;
|
||||
}
|
||||
return $expiry_time;
|
||||
case 'domain':
|
||||
// Domains are not required as per RFC 6265 section 5.2.3
|
||||
if (empty($value)) {
|
||||
return null;
|
||||
}
|
||||
// Domain normalization, as per RFC 6265 section 5.2.3
|
||||
if ($value[0] === '.') {
|
||||
$value = \substr($value, 1);
|
||||
}
|
||||
return $value;
|
||||
default:
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Format a cookie for a Cookie header
|
||||
*
|
||||
* This is used when sending cookies to a server.
|
||||
*
|
||||
* @return string Cookie formatted for Cookie header
|
||||
*/
|
||||
public function format_for_header()
|
||||
{
|
||||
return \sprintf('%s=%s', $this->name, $this->value);
|
||||
}
|
||||
/**
|
||||
* Format a cookie for a Set-Cookie header
|
||||
*
|
||||
* This is used when sending cookies to clients. This isn't really
|
||||
* applicable to client-side usage, but might be handy for debugging.
|
||||
*
|
||||
* @return string Cookie formatted for Set-Cookie header
|
||||
*/
|
||||
public function format_for_set_cookie()
|
||||
{
|
||||
$header_value = $this->format_for_header();
|
||||
if (!empty($this->attributes)) {
|
||||
$parts = [];
|
||||
foreach ($this->attributes as $key => $value) {
|
||||
// Ignore non-associative attributes
|
||||
if (\is_numeric($key)) {
|
||||
$parts[] = $value;
|
||||
} else {
|
||||
$parts[] = \sprintf('%s=%s', $key, $value);
|
||||
}
|
||||
}
|
||||
$header_value .= '; ' . \implode('; ', $parts);
|
||||
}
|
||||
return $header_value;
|
||||
}
|
||||
/**
|
||||
* Parse a cookie string into a cookie object
|
||||
*
|
||||
* Based on Mozilla's parsing code in Firefox and related projects, which
|
||||
* is an intentional deviation from RFC 2109 and RFC 2616. RFC 6265
|
||||
* specifies some of this handling, but not in a thorough manner.
|
||||
*
|
||||
* @param string $cookie_header Cookie header value (from a Set-Cookie header)
|
||||
* @param string $name
|
||||
* @param int|null $reference_time
|
||||
* @return \VendorDuplicator\WpOrg\Requests\Cookie Parsed cookie object
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $cookie_header argument is not a string.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $name argument is not a string.
|
||||
*/
|
||||
public static function parse($cookie_header, $name = '', $reference_time = null)
|
||||
{
|
||||
if (\is_string($cookie_header) === \false) {
|
||||
throw InvalidArgument::create(1, '$cookie_header', 'string', \gettype($cookie_header));
|
||||
}
|
||||
if (\is_string($name) === \false) {
|
||||
throw InvalidArgument::create(2, '$name', 'string', \gettype($name));
|
||||
}
|
||||
$parts = \explode(';', $cookie_header);
|
||||
$kvparts = \array_shift($parts);
|
||||
if (!empty($name)) {
|
||||
$value = $cookie_header;
|
||||
} elseif (\strpos($kvparts, '=') === \false) {
|
||||
// Some sites might only have a value without the equals separator.
|
||||
// Deviate from RFC 6265 and pretend it was actually a blank name
|
||||
// (`=foo`)
|
||||
//
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=169091
|
||||
$name = '';
|
||||
$value = $kvparts;
|
||||
} else {
|
||||
list($name, $value) = \explode('=', $kvparts, 2);
|
||||
}
|
||||
$name = \trim($name);
|
||||
$value = \trim($value);
|
||||
// Attribute keys are handled case-insensitively
|
||||
$attributes = new CaseInsensitiveDictionary();
|
||||
if (!empty($parts)) {
|
||||
foreach ($parts as $part) {
|
||||
if (\strpos($part, '=') === \false) {
|
||||
$part_key = $part;
|
||||
$part_value = \true;
|
||||
} else {
|
||||
list($part_key, $part_value) = \explode('=', $part, 2);
|
||||
$part_value = \trim($part_value);
|
||||
}
|
||||
$part_key = \trim($part_key);
|
||||
$attributes[$part_key] = $part_value;
|
||||
}
|
||||
}
|
||||
return new static($name, $value, $attributes, [], $reference_time);
|
||||
}
|
||||
/**
|
||||
* Parse all Set-Cookie headers from request headers
|
||||
*
|
||||
* @param \VendorDuplicator\WpOrg\Requests\Response\Headers $headers Headers to parse from
|
||||
* @param \VendorDuplicator\WpOrg\Requests\Iri|null $origin URI for comparing cookie origins
|
||||
* @param int|null $time Reference time for expiration calculation
|
||||
* @return array
|
||||
*/
|
||||
public static function parse_from_headers(Headers $headers, Iri $origin = null, $time = null)
|
||||
{
|
||||
$cookie_headers = $headers->getValues('Set-Cookie');
|
||||
if (empty($cookie_headers)) {
|
||||
return [];
|
||||
}
|
||||
$cookies = [];
|
||||
foreach ($cookie_headers as $header) {
|
||||
$parsed = self::parse($header, '', $time);
|
||||
// Default domain/path attributes
|
||||
if (empty($parsed->attributes['domain']) && !empty($origin)) {
|
||||
$parsed->attributes['domain'] = $origin->host;
|
||||
$parsed->flags['host-only'] = \true;
|
||||
} else {
|
||||
$parsed->flags['host-only'] = \false;
|
||||
}
|
||||
$path_is_valid = !empty($parsed->attributes['path']) && $parsed->attributes['path'][0] === '/';
|
||||
if (!$path_is_valid && !empty($origin)) {
|
||||
$path = $origin->path;
|
||||
// Default path normalization as per RFC 6265 section 5.1.4
|
||||
if (\substr($path, 0, 1) !== '/') {
|
||||
// If the uri-path is empty or if the first character of
|
||||
// the uri-path is not a %x2F ("/") character, output
|
||||
// %x2F ("/") and skip the remaining steps.
|
||||
$path = '/';
|
||||
} elseif (\substr_count($path, '/') === 1) {
|
||||
// If the uri-path contains no more than one %x2F ("/")
|
||||
// character, output %x2F ("/") and skip the remaining
|
||||
// step.
|
||||
$path = '/';
|
||||
} else {
|
||||
// Output the characters of the uri-path from the first
|
||||
// character up to, but not including, the right-most
|
||||
// %x2F ("/").
|
||||
$path = \substr($path, 0, \strrpos($path, '/'));
|
||||
}
|
||||
$parsed->attributes['path'] = $path;
|
||||
}
|
||||
// Reject invalid cookie domains
|
||||
if (!empty($origin) && !$parsed->domain_matches($origin->host)) {
|
||||
continue;
|
||||
}
|
||||
$cookies[$parsed->name] = $parsed;
|
||||
}
|
||||
return $cookies;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Cookie holder object
|
||||
*
|
||||
* @package Requests\Cookies
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Cookie;
|
||||
|
||||
use ArrayAccess;
|
||||
use ArrayIterator;
|
||||
use IteratorAggregate;
|
||||
use ReturnTypeWillChange;
|
||||
use VendorDuplicator\WpOrg\Requests\Cookie;
|
||||
use VendorDuplicator\WpOrg\Requests\Exception;
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument;
|
||||
use VendorDuplicator\WpOrg\Requests\HookManager;
|
||||
use VendorDuplicator\WpOrg\Requests\Iri;
|
||||
use VendorDuplicator\WpOrg\Requests\Response;
|
||||
/**
|
||||
* Cookie holder object
|
||||
*
|
||||
* @package Requests\Cookies
|
||||
*/
|
||||
class Jar implements ArrayAccess, IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* Actual item data
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $cookies = [];
|
||||
/**
|
||||
* Create a new jar
|
||||
*
|
||||
* @param array $cookies Existing cookie values
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed argument is not an array.
|
||||
*/
|
||||
public function __construct($cookies = [])
|
||||
{
|
||||
if (\is_array($cookies) === \false) {
|
||||
throw InvalidArgument::create(1, '$cookies', 'array', \gettype($cookies));
|
||||
}
|
||||
$this->cookies = $cookies;
|
||||
}
|
||||
/**
|
||||
* Normalise cookie data into a \WpOrg\Requests\Cookie
|
||||
*
|
||||
* @param string|\WpOrg\Requests\Cookie $cookie Cookie header value, possibly pre-parsed (object).
|
||||
* @param string $key Optional. The name for this cookie.
|
||||
* @return \VendorDuplicator\WpOrg\Requests\Cookie
|
||||
*/
|
||||
public function normalize_cookie($cookie, $key = '')
|
||||
{
|
||||
if ($cookie instanceof Cookie) {
|
||||
return $cookie;
|
||||
}
|
||||
return Cookie::parse($cookie, $key);
|
||||
}
|
||||
/**
|
||||
* Check if the given item exists
|
||||
*
|
||||
* @param string $offset Item key
|
||||
* @return boolean Does the item exist?
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return isset($this->cookies[$offset]);
|
||||
}
|
||||
/**
|
||||
* Get the value for the item
|
||||
*
|
||||
* @param string $offset Item key
|
||||
* @return string|null Item value (null if offsetExists is false)
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
if (!isset($this->cookies[$offset])) {
|
||||
return null;
|
||||
}
|
||||
return $this->cookies[$offset];
|
||||
}
|
||||
/**
|
||||
* Set the given item
|
||||
*
|
||||
* @param string $offset Item name
|
||||
* @param string $value Item value
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception On attempting to use dictionary as list (`invalidset`)
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
if ($offset === null) {
|
||||
throw new Exception('Object is a dictionary, not a list', 'invalidset');
|
||||
}
|
||||
$this->cookies[$offset] = $value;
|
||||
}
|
||||
/**
|
||||
* Unset the given header
|
||||
*
|
||||
* @param string $offset The key for the item to unset.
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
unset($this->cookies[$offset]);
|
||||
}
|
||||
/**
|
||||
* Get an iterator for the data
|
||||
*
|
||||
* @return \ArrayIterator
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function getIterator()
|
||||
{
|
||||
return new ArrayIterator($this->cookies);
|
||||
}
|
||||
/**
|
||||
* Register the cookie handler with the request's hooking system
|
||||
*
|
||||
* @param \VendorDuplicator\WpOrg\Requests\HookManager $hooks Hooking system
|
||||
*/
|
||||
public function register(HookManager $hooks)
|
||||
{
|
||||
$hooks->register('requests.before_request', [$this, 'before_request']);
|
||||
$hooks->register('requests.before_redirect_check', [$this, 'before_redirect_check']);
|
||||
}
|
||||
/**
|
||||
* Add Cookie header to a request if we have any
|
||||
*
|
||||
* As per RFC 6265, cookies are separated by '; '
|
||||
*
|
||||
* @param string $url
|
||||
* @param array $headers
|
||||
* @param array $data
|
||||
* @param string $type
|
||||
* @param array $options
|
||||
*/
|
||||
public function before_request($url, &$headers, &$data, &$type, &$options)
|
||||
{
|
||||
if (!$url instanceof Iri) {
|
||||
$url = new Iri($url);
|
||||
}
|
||||
if (!empty($this->cookies)) {
|
||||
$cookies = [];
|
||||
foreach ($this->cookies as $key => $cookie) {
|
||||
$cookie = $this->normalize_cookie($cookie, $key);
|
||||
// Skip expired cookies
|
||||
if ($cookie->is_expired()) {
|
||||
continue;
|
||||
}
|
||||
if ($cookie->domain_matches($url->host)) {
|
||||
$cookies[] = $cookie->format_for_header();
|
||||
}
|
||||
}
|
||||
$headers['Cookie'] = \implode('; ', $cookies);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Parse all cookies from a response and attach them to the response
|
||||
*
|
||||
* @param \VendorDuplicator\WpOrg\Requests\Response $response Response as received.
|
||||
*/
|
||||
public function before_redirect_check(Response $response)
|
||||
{
|
||||
$url = $response->url;
|
||||
if (!$url instanceof Iri) {
|
||||
$url = new Iri($url);
|
||||
}
|
||||
$cookies = Cookie::parse_from_headers($response->headers, $url);
|
||||
$this->cookies = \array_merge($this->cookies, $cookies);
|
||||
$response->cookies = $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for HTTP requests
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests;
|
||||
|
||||
use Exception as PHPException;
|
||||
/**
|
||||
* Exception for HTTP requests
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
class Exception extends PHPException
|
||||
{
|
||||
/**
|
||||
* Type of exception
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
/**
|
||||
* Data associated with the exception
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $data;
|
||||
/**
|
||||
* Create a new exception
|
||||
*
|
||||
* @param string $message Exception message
|
||||
* @param string $type Exception type
|
||||
* @param mixed $data Associated data
|
||||
* @param integer $code Exception numerical code, if applicable
|
||||
*/
|
||||
public function __construct($message, $type, $data = null, $code = 0)
|
||||
{
|
||||
parent::__construct($message, $code);
|
||||
$this->type = $type;
|
||||
$this->data = $data;
|
||||
}
|
||||
/**
|
||||
* Like {@see \Exception::getCode()}, but a string code.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @return string
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
/**
|
||||
* Gives any relevant data
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @return mixed
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception;
|
||||
/**
|
||||
* Exception for when an incorrect number of arguments are passed to a method.
|
||||
*
|
||||
* Typically, this exception is used when all arguments for a method are optional,
|
||||
* but certain arguments need to be passed together, i.e. a method which can be called
|
||||
* with no arguments or with two arguments, but not with one argument.
|
||||
*
|
||||
* Along the same lines, this exception is also used if a method expects an array
|
||||
* with a certain number of elements and the provided number of elements does not comply.
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
* @since 2.0.0
|
||||
*/
|
||||
final class ArgumentCount extends Exception
|
||||
{
|
||||
/**
|
||||
* Create a new argument count exception with a standardized text.
|
||||
*
|
||||
* @param string $expected The argument count expected as a phrase.
|
||||
* For example: `at least 2 arguments` or `exactly 1 argument`.
|
||||
* @param int $received The actual argument count received.
|
||||
* @param string $type Exception type.
|
||||
*
|
||||
* @return \VendorDuplicator\WpOrg\Requests\Exception\ArgumentCount
|
||||
*/
|
||||
public static function create($expected, $received, $type)
|
||||
{
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
|
||||
$stack = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 2);
|
||||
return new self(\sprintf('%s::%s() expects %s, %d given', $stack[1]['class'], $stack[1]['function'], $expected, $received), $type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception based on HTTP response
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception;
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http\StatusUnknown;
|
||||
/**
|
||||
* Exception based on HTTP response
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
class Http extends Exception
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 0;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Unknown';
|
||||
/**
|
||||
* Create a new exception
|
||||
*
|
||||
* There is no mechanism to pass in the status code, as this is set by the
|
||||
* subclass used. Reason phrases can vary, however.
|
||||
*
|
||||
* @param string|null $reason Reason phrase
|
||||
* @param mixed $data Associated data
|
||||
*/
|
||||
public function __construct($reason = null, $data = null)
|
||||
{
|
||||
if ($reason !== null) {
|
||||
$this->reason = $reason;
|
||||
}
|
||||
$message = \sprintf('%d %s', $this->code, $this->reason);
|
||||
parent::__construct($message, 'httpresponse', $data, $this->code);
|
||||
}
|
||||
/**
|
||||
* Get the status message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getReason()
|
||||
{
|
||||
return $this->reason;
|
||||
}
|
||||
/**
|
||||
* Get the correct exception class for a given error code
|
||||
*
|
||||
* @param int|bool $code HTTP status code, or false if unavailable
|
||||
* @return string Exception class name to use
|
||||
*/
|
||||
public static function get_class($code)
|
||||
{
|
||||
if (!$code) {
|
||||
return StatusUnknown::class;
|
||||
}
|
||||
$class = \sprintf('\\WpOrg\\Requests\\Exception\\Http\\Status%d', $code);
|
||||
if (\class_exists($class)) {
|
||||
return $class;
|
||||
}
|
||||
return StatusUnknown::class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 304 Not Modified responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 304 Not Modified responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status304 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 304;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Not Modified';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 305 Use Proxy responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 305 Use Proxy responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status305 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 305;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Use Proxy';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 306 Switch Proxy responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 306 Switch Proxy responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status306 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 306;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Switch Proxy';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 400 Bad Request responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 400 Bad Request responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status400 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 400;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Bad Request';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 401 Unauthorized responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 401 Unauthorized responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status401 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 401;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Unauthorized';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 402 Payment Required responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 402 Payment Required responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status402 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 402;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Payment Required';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 403 Forbidden responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 403 Forbidden responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status403 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 403;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Forbidden';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 404 Not Found responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 404 Not Found responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status404 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 404;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Not Found';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 405 Method Not Allowed responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 405 Method Not Allowed responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status405 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 405;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Method Not Allowed';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 406 Not Acceptable responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 406 Not Acceptable responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status406 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 406;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Not Acceptable';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 407 Proxy Authentication Required responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 407 Proxy Authentication Required responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status407 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 407;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Proxy Authentication Required';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 408 Request Timeout responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 408 Request Timeout responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status408 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 408;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Request Timeout';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 409 Conflict responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 409 Conflict responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status409 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 409;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Conflict';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 410 Gone responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 410 Gone responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status410 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 410;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Gone';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 411 Length Required responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 411 Length Required responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status411 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 411;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Length Required';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 412 Precondition Failed responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 412 Precondition Failed responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status412 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 412;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Precondition Failed';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 413 Request Entity Too Large responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 413 Request Entity Too Large responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status413 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 413;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Request Entity Too Large';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 414 Request-URI Too Large responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 414 Request-URI Too Large responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status414 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 414;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Request-URI Too Large';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 415 Unsupported Media Type responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 415 Unsupported Media Type responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status415 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 415;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Unsupported Media Type';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 416 Requested Range Not Satisfiable responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 416 Requested Range Not Satisfiable responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status416 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 416;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Requested Range Not Satisfiable';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 417 Expectation Failed responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 417 Expectation Failed responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status417 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 417;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Expectation Failed';
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 418 I'm A Teapot responses
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc2324
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 418 I'm A Teapot responses
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc2324
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status418 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 418;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = "I'm A Teapot";
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 428 Precondition Required responses
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc6585
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 428 Precondition Required responses
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc6585
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status428 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 428;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Precondition Required';
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 429 Too Many Requests responses
|
||||
*
|
||||
* @link https://tools.ietf.org/html/draft-nottingham-http-new-status-04
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 429 Too Many Requests responses
|
||||
*
|
||||
* @link https://tools.ietf.org/html/draft-nottingham-http-new-status-04
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status429 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 429;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Too Many Requests';
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 431 Request Header Fields Too Large responses
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc6585
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 431 Request Header Fields Too Large responses
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc6585
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status431 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 431;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Request Header Fields Too Large';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 500 Internal Server Error responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 500 Internal Server Error responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status500 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 500;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Internal Server Error';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 501 Not Implemented responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 501 Not Implemented responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status501 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 501;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Not Implemented';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 502 Bad Gateway responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 502 Bad Gateway responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status502 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 502;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Bad Gateway';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 503 Service Unavailable responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 503 Service Unavailable responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status503 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 503;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Service Unavailable';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 504 Gateway Timeout responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 504 Gateway Timeout responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status504 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 504;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Gateway Timeout';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 505 HTTP Version Not Supported responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 505 HTTP Version Not Supported responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status505 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 505;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'HTTP Version Not Supported';
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for 511 Network Authentication Required responses
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc6585
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
/**
|
||||
* Exception for 511 Network Authentication Required responses
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc6585
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Status511 extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = 511;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Network Authentication Required';
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception for unknown status responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
use VendorDuplicator\WpOrg\Requests\Response;
|
||||
/**
|
||||
* Exception for unknown status responses
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class StatusUnknown extends Http
|
||||
{
|
||||
/**
|
||||
* HTTP status code
|
||||
*
|
||||
* @var integer|bool Code if available, false if an error occurred
|
||||
*/
|
||||
protected $code = 0;
|
||||
/**
|
||||
* Reason phrase
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Unknown';
|
||||
/**
|
||||
* Create a new exception
|
||||
*
|
||||
* If `$data` is an instance of {@see \WpOrg\Requests\Response}, uses the status
|
||||
* code from it. Otherwise, sets as 0
|
||||
*
|
||||
* @param string|null $reason Reason phrase
|
||||
* @param mixed $data Associated data
|
||||
*/
|
||||
public function __construct($reason = null, $data = null)
|
||||
{
|
||||
if ($data instanceof Response) {
|
||||
$this->code = (int) $data->status_code;
|
||||
}
|
||||
parent::__construct($reason, $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception;
|
||||
|
||||
use InvalidArgumentException;
|
||||
/**
|
||||
* Exception for an invalid argument passed.
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
* @since 2.0.0
|
||||
*/
|
||||
final class InvalidArgument extends InvalidArgumentException
|
||||
{
|
||||
/**
|
||||
* Create a new invalid argument exception with a standardized text.
|
||||
*
|
||||
* @param int $position The argument position in the function signature. 1-based.
|
||||
* @param string $name The argument name in the function signature.
|
||||
* @param string $expected The argument type expected as a string.
|
||||
* @param string $received The actual argument type received.
|
||||
*
|
||||
* @return \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument
|
||||
*/
|
||||
public static function create($position, $name, $expected, $received)
|
||||
{
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
|
||||
$stack = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 2);
|
||||
return new self(\sprintf('%s::%s(): Argument #%d (%s) must be of type %s, %s given', $stack[1]['class'], $stack[1]['function'], $position, $name, $expected, $received));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Transport Exception
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception;
|
||||
/**
|
||||
* Transport Exception
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
class Transport extends Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* CURL Transport Exception.
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Exception\Transport;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Transport;
|
||||
/**
|
||||
* CURL Transport Exception.
|
||||
*
|
||||
* @package Requests\Exceptions
|
||||
*/
|
||||
final class Curl extends Transport
|
||||
{
|
||||
const EASY = 'cURLEasy';
|
||||
const MULTI = 'cURLMulti';
|
||||
const SHARE = 'cURLShare';
|
||||
/**
|
||||
* cURL error code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $code = -1;
|
||||
/**
|
||||
* Which type of cURL error
|
||||
*
|
||||
* EASY|MULTI|SHARE
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'Unknown';
|
||||
/**
|
||||
* Clear text error message
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason = 'Unknown';
|
||||
/**
|
||||
* Create a new exception.
|
||||
*
|
||||
* @param string $message Exception message.
|
||||
* @param string $type Exception type.
|
||||
* @param mixed $data Associated data, if applicable.
|
||||
* @param int $code Exception numerical code, if applicable.
|
||||
*/
|
||||
public function __construct($message, $type, $data = null, $code = 0)
|
||||
{
|
||||
if ($type !== null) {
|
||||
$this->type = $type;
|
||||
}
|
||||
if ($code !== null) {
|
||||
$this->code = (int) $code;
|
||||
}
|
||||
if ($message !== null) {
|
||||
$this->reason = $message;
|
||||
}
|
||||
$message = \sprintf('%d %s', $this->code, $this->reason);
|
||||
parent::__construct($message, $this->type, $data, $this->code);
|
||||
}
|
||||
/**
|
||||
* Get the error message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getReason()
|
||||
{
|
||||
return $this->reason;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Event dispatcher
|
||||
*
|
||||
* @package Requests\EventDispatcher
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests;
|
||||
|
||||
/**
|
||||
* Event dispatcher
|
||||
*
|
||||
* @package Requests\EventDispatcher
|
||||
*/
|
||||
interface HookManager
|
||||
{
|
||||
/**
|
||||
* Register a callback for a hook
|
||||
*
|
||||
* @param string $hook Hook name
|
||||
* @param callable $callback Function/method to call on event
|
||||
* @param int $priority Priority number. <0 is executed earlier, >0 is executed later
|
||||
*/
|
||||
public function register($hook, $callback, $priority = 0);
|
||||
/**
|
||||
* Dispatch a message
|
||||
*
|
||||
* @param string $hook Hook name
|
||||
* @param array $parameters Parameters to pass to callbacks
|
||||
* @return boolean Successfulness
|
||||
*/
|
||||
public function dispatch($hook, $parameters = []);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Handles adding and dispatching events
|
||||
*
|
||||
* @package Requests\EventDispatcher
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument;
|
||||
use VendorDuplicator\WpOrg\Requests\HookManager;
|
||||
use VendorDuplicator\WpOrg\Requests\Utility\InputValidator;
|
||||
/**
|
||||
* Handles adding and dispatching events
|
||||
*
|
||||
* @package Requests\EventDispatcher
|
||||
*/
|
||||
class Hooks implements HookManager
|
||||
{
|
||||
/**
|
||||
* Registered callbacks for each hook
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $hooks = [];
|
||||
/**
|
||||
* Register a callback for a hook
|
||||
*
|
||||
* @param string $hook Hook name
|
||||
* @param callable $callback Function/method to call on event
|
||||
* @param int $priority Priority number. <0 is executed earlier, >0 is executed later
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $hook argument is not a string.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $callback argument is not callable.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $priority argument is not an integer.
|
||||
*/
|
||||
public function register($hook, $callback, $priority = 0)
|
||||
{
|
||||
if (\is_string($hook) === \false) {
|
||||
throw InvalidArgument::create(1, '$hook', 'string', \gettype($hook));
|
||||
}
|
||||
if (\is_callable($callback) === \false) {
|
||||
throw InvalidArgument::create(2, '$callback', 'callable', \gettype($callback));
|
||||
}
|
||||
if (InputValidator::is_numeric_array_key($priority) === \false) {
|
||||
throw InvalidArgument::create(3, '$priority', 'integer', \gettype($priority));
|
||||
}
|
||||
if (!isset($this->hooks[$hook])) {
|
||||
$this->hooks[$hook] = [$priority => []];
|
||||
} elseif (!isset($this->hooks[$hook][$priority])) {
|
||||
$this->hooks[$hook][$priority] = [];
|
||||
}
|
||||
$this->hooks[$hook][$priority][] = $callback;
|
||||
}
|
||||
/**
|
||||
* Dispatch a message
|
||||
*
|
||||
* @param string $hook Hook name
|
||||
* @param array $parameters Parameters to pass to callbacks
|
||||
* @return boolean Successfulness
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $hook argument is not a string.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $parameters argument is not an array.
|
||||
*/
|
||||
public function dispatch($hook, $parameters = [])
|
||||
{
|
||||
if (\is_string($hook) === \false) {
|
||||
throw InvalidArgument::create(1, '$hook', 'string', \gettype($hook));
|
||||
}
|
||||
// Check strictly against array, as Array* objects don't work in combination with `call_user_func_array()`.
|
||||
if (\is_array($parameters) === \false) {
|
||||
throw InvalidArgument::create(2, '$parameters', 'array', \gettype($parameters));
|
||||
}
|
||||
if (empty($this->hooks[$hook])) {
|
||||
return \false;
|
||||
}
|
||||
if (!empty($parameters)) {
|
||||
// Strip potential keys from the array to prevent them being interpreted as parameter names in PHP 8.0.
|
||||
$parameters = \array_values($parameters);
|
||||
}
|
||||
\ksort($this->hooks[$hook]);
|
||||
foreach ($this->hooks[$hook] as $priority => $hooked) {
|
||||
foreach ($hooked as $callback) {
|
||||
$callback(...$parameters);
|
||||
}
|
||||
}
|
||||
return \true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
<?php
|
||||
|
||||
namespace VendorDuplicator\WpOrg\Requests;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception;
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument;
|
||||
use VendorDuplicator\WpOrg\Requests\Utility\InputValidator;
|
||||
/**
|
||||
* IDNA URL encoder
|
||||
*
|
||||
* Note: Not fully compliant, as nameprep does nothing yet.
|
||||
*
|
||||
* @package Requests\Utilities
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc3490 IDNA specification
|
||||
* @link https://tools.ietf.org/html/rfc3492 Punycode/Bootstrap specification
|
||||
*/
|
||||
class IdnaEncoder
|
||||
{
|
||||
/**
|
||||
* ACE prefix used for IDNA
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc3490#section-5
|
||||
* @var string
|
||||
*/
|
||||
const ACE_PREFIX = 'xn--';
|
||||
/**
|
||||
* Maximum length of a IDNA URL in ASCII.
|
||||
*
|
||||
* @see \VendorDuplicator\WpOrg\Requests\IdnaEncoder::to_ascii()
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const MAX_LENGTH = 64;
|
||||
/**#@+
|
||||
* Bootstrap constant for Punycode
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc3492#section-5
|
||||
* @var int
|
||||
*/
|
||||
const BOOTSTRAP_BASE = 36;
|
||||
const BOOTSTRAP_TMIN = 1;
|
||||
const BOOTSTRAP_TMAX = 26;
|
||||
const BOOTSTRAP_SKEW = 38;
|
||||
const BOOTSTRAP_DAMP = 700;
|
||||
const BOOTSTRAP_INITIAL_BIAS = 72;
|
||||
const BOOTSTRAP_INITIAL_N = 128;
|
||||
/**#@-*/
|
||||
/**
|
||||
* Encode a hostname using Punycode
|
||||
*
|
||||
* @param string|Stringable $hostname Hostname
|
||||
* @return string Punycode-encoded hostname
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string or a stringable object.
|
||||
*/
|
||||
public static function encode($hostname)
|
||||
{
|
||||
if (InputValidator::is_string_or_stringable($hostname) === \false) {
|
||||
throw InvalidArgument::create(1, '$hostname', 'string|Stringable', \gettype($hostname));
|
||||
}
|
||||
$parts = \explode('.', $hostname);
|
||||
foreach ($parts as &$part) {
|
||||
$part = self::to_ascii($part);
|
||||
}
|
||||
return \implode('.', $parts);
|
||||
}
|
||||
/**
|
||||
* Convert a UTF-8 text string to an ASCII string using Punycode
|
||||
*
|
||||
* @param string $text ASCII or UTF-8 string (max length 64 characters)
|
||||
* @return string ASCII string
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception Provided string longer than 64 ASCII characters (`idna.provided_too_long`)
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception Prepared string longer than 64 ASCII characters (`idna.prepared_too_long`)
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception Provided string already begins with xn-- (`idna.provided_is_prefixed`)
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception Encoded string longer than 64 ASCII characters (`idna.encoded_too_long`)
|
||||
*/
|
||||
public static function to_ascii($text)
|
||||
{
|
||||
// Step 1: Check if the text is already ASCII
|
||||
if (self::is_ascii($text)) {
|
||||
// Skip to step 7
|
||||
if (\strlen($text) < self::MAX_LENGTH) {
|
||||
return $text;
|
||||
}
|
||||
throw new Exception('Provided string is too long', 'idna.provided_too_long', $text);
|
||||
}
|
||||
// Step 2: nameprep
|
||||
$text = self::nameprep($text);
|
||||
// Step 3: UseSTD3ASCIIRules is false, continue
|
||||
// Step 4: Check if it's ASCII now
|
||||
if (self::is_ascii($text)) {
|
||||
// Skip to step 7
|
||||
/*
|
||||
* As the `nameprep()` method returns the original string, this code will never be reached until
|
||||
* that method is properly implemented.
|
||||
*/
|
||||
// @codeCoverageIgnoreStart
|
||||
if (\strlen($text) < self::MAX_LENGTH) {
|
||||
return $text;
|
||||
}
|
||||
throw new Exception('Prepared string is too long', 'idna.prepared_too_long', $text);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
// Step 5: Check ACE prefix
|
||||
if (\strpos($text, self::ACE_PREFIX) === 0) {
|
||||
throw new Exception('Provided string begins with ACE prefix', 'idna.provided_is_prefixed', $text);
|
||||
}
|
||||
// Step 6: Encode with Punycode
|
||||
$text = self::punycode_encode($text);
|
||||
// Step 7: Prepend ACE prefix
|
||||
$text = self::ACE_PREFIX . $text;
|
||||
// Step 8: Check size
|
||||
if (\strlen($text) < self::MAX_LENGTH) {
|
||||
return $text;
|
||||
}
|
||||
throw new Exception('Encoded string is too long', 'idna.encoded_too_long', $text);
|
||||
}
|
||||
/**
|
||||
* Check whether a given text string contains only ASCII characters
|
||||
*
|
||||
* @internal (Testing found regex was the fastest implementation)
|
||||
*
|
||||
* @param string $text Text to examine.
|
||||
* @return bool Is the text string ASCII-only?
|
||||
*/
|
||||
protected static function is_ascii($text)
|
||||
{
|
||||
return \preg_match('/(?:[^\\x00-\\x7F])/', $text) !== 1;
|
||||
}
|
||||
/**
|
||||
* Prepare a text string for use as an IDNA name
|
||||
*
|
||||
* @todo Implement this based on RFC 3491 and the newer 5891
|
||||
* @param string $text Text to prepare.
|
||||
* @return string Prepared string
|
||||
*/
|
||||
protected static function nameprep($text)
|
||||
{
|
||||
return $text;
|
||||
}
|
||||
/**
|
||||
* Convert a UTF-8 string to a UCS-4 codepoint array
|
||||
*
|
||||
* Based on \WpOrg\Requests\Iri::replace_invalid_with_pct_encoding()
|
||||
*
|
||||
* @param string $input Text to convert.
|
||||
* @return array Unicode code points
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception Invalid UTF-8 codepoint (`idna.invalidcodepoint`)
|
||||
*/
|
||||
protected static function utf8_to_codepoints($input)
|
||||
{
|
||||
$codepoints = [];
|
||||
// Get number of bytes
|
||||
$strlen = \strlen($input);
|
||||
// phpcs:ignore Generic.CodeAnalysis.JumbledIncrementer -- This is a deliberate choice.
|
||||
for ($position = 0; $position < $strlen; $position++) {
|
||||
$value = \ord($input[$position]);
|
||||
if ((~$value & 0x80) === 0x80) {
|
||||
// One byte sequence:
|
||||
$character = $value;
|
||||
$length = 1;
|
||||
$remaining = 0;
|
||||
} elseif (($value & 0xe0) === 0xc0) {
|
||||
// Two byte sequence:
|
||||
$character = ($value & 0x1f) << 6;
|
||||
$length = 2;
|
||||
$remaining = 1;
|
||||
} elseif (($value & 0xf0) === 0xe0) {
|
||||
// Three byte sequence:
|
||||
$character = ($value & 0xf) << 12;
|
||||
$length = 3;
|
||||
$remaining = 2;
|
||||
} elseif (($value & 0xf8) === 0xf0) {
|
||||
// Four byte sequence:
|
||||
$character = ($value & 0x7) << 18;
|
||||
$length = 4;
|
||||
$remaining = 3;
|
||||
} else {
|
||||
// Invalid byte:
|
||||
throw new Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $value);
|
||||
}
|
||||
if ($remaining > 0) {
|
||||
if ($position + $length > $strlen) {
|
||||
throw new Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
|
||||
}
|
||||
for ($position++; $remaining > 0; $position++) {
|
||||
$value = \ord($input[$position]);
|
||||
// If it is invalid, count the sequence as invalid and reprocess the current byte:
|
||||
if (($value & 0xc0) !== 0x80) {
|
||||
throw new Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
|
||||
}
|
||||
--$remaining;
|
||||
$character |= ($value & 0x3f) << $remaining * 6;
|
||||
}
|
||||
$position--;
|
||||
}
|
||||
if ($length > 1 && $character <= 0x7f || $length > 2 && $character <= 0x7ff || $length > 3 && $character <= 0xffff || ($character & 0xfffe) === 0xfffe || $character >= 0xfdd0 && $character <= 0xfdef || ($character > 0xd7ff && $character < 0xf900 || $character < 0x20 || $character > 0x7e && $character < 0xa0 || $character > 0xefffd)) {
|
||||
throw new Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
|
||||
}
|
||||
$codepoints[] = $character;
|
||||
}
|
||||
return $codepoints;
|
||||
}
|
||||
/**
|
||||
* RFC3492-compliant encoder
|
||||
*
|
||||
* @internal Pseudo-code from Section 6.3 is commented with "#" next to relevant code
|
||||
*
|
||||
* @param string $input UTF-8 encoded string to encode
|
||||
* @return string Punycode-encoded string
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception On character outside of the domain (never happens with Punycode) (`idna.character_outside_domain`)
|
||||
*/
|
||||
public static function punycode_encode($input)
|
||||
{
|
||||
$output = '';
|
||||
// let n = initial_n
|
||||
$n = self::BOOTSTRAP_INITIAL_N;
|
||||
// let delta = 0
|
||||
$delta = 0;
|
||||
// let bias = initial_bias
|
||||
$bias = self::BOOTSTRAP_INITIAL_BIAS;
|
||||
// let h = b = the number of basic code points in the input
|
||||
$h = 0;
|
||||
$b = 0;
|
||||
// see loop
|
||||
// copy them to the output in order
|
||||
$codepoints = self::utf8_to_codepoints($input);
|
||||
$extended = [];
|
||||
foreach ($codepoints as $char) {
|
||||
if ($char < 128) {
|
||||
// Character is valid ASCII
|
||||
// TODO: this should also check if it's valid for a URL
|
||||
$output .= \chr($char);
|
||||
$h++;
|
||||
// Check if the character is non-ASCII, but below initial n
|
||||
// This never occurs for Punycode, so ignore in coverage
|
||||
// @codeCoverageIgnoreStart
|
||||
} elseif ($char < $n) {
|
||||
throw new Exception('Invalid character', 'idna.character_outside_domain', $char);
|
||||
// @codeCoverageIgnoreEnd
|
||||
} else {
|
||||
$extended[$char] = \true;
|
||||
}
|
||||
}
|
||||
$extended = \array_keys($extended);
|
||||
\sort($extended);
|
||||
$b = $h;
|
||||
// [copy them] followed by a delimiter if b > 0
|
||||
if (\strlen($output) > 0) {
|
||||
$output .= '-';
|
||||
}
|
||||
// {if the input contains a non-basic code point < n then fail}
|
||||
// while h < length(input) do begin
|
||||
$codepointcount = \count($codepoints);
|
||||
while ($h < $codepointcount) {
|
||||
// let m = the minimum code point >= n in the input
|
||||
$m = \array_shift($extended);
|
||||
//printf('next code point to insert is %s' . PHP_EOL, dechex($m));
|
||||
// let delta = delta + (m - n) * (h + 1), fail on overflow
|
||||
$delta += ($m - $n) * ($h + 1);
|
||||
// let n = m
|
||||
$n = $m;
|
||||
// for each code point c in the input (in order) do begin
|
||||
for ($num = 0; $num < $codepointcount; $num++) {
|
||||
$c = $codepoints[$num];
|
||||
// if c < n then increment delta, fail on overflow
|
||||
if ($c < $n) {
|
||||
$delta++;
|
||||
} elseif ($c === $n) {
|
||||
// if c == n then begin
|
||||
// let q = delta
|
||||
$q = $delta;
|
||||
// for k = base to infinity in steps of base do begin
|
||||
for ($k = self::BOOTSTRAP_BASE;; $k += self::BOOTSTRAP_BASE) {
|
||||
// let t = tmin if k <= bias {+ tmin}, or
|
||||
// tmax if k >= bias + tmax, or k - bias otherwise
|
||||
if ($k <= $bias + self::BOOTSTRAP_TMIN) {
|
||||
$t = self::BOOTSTRAP_TMIN;
|
||||
} elseif ($k >= $bias + self::BOOTSTRAP_TMAX) {
|
||||
$t = self::BOOTSTRAP_TMAX;
|
||||
} else {
|
||||
$t = $k - $bias;
|
||||
}
|
||||
// if q < t then break
|
||||
if ($q < $t) {
|
||||
break;
|
||||
}
|
||||
// output the code point for digit t + ((q - t) mod (base - t))
|
||||
$digit = (int) ($t + ($q - $t) % (self::BOOTSTRAP_BASE - $t));
|
||||
$output .= self::digit_to_char($digit);
|
||||
// let q = (q - t) div (base - t)
|
||||
$q = (int) \floor(($q - $t) / (self::BOOTSTRAP_BASE - $t));
|
||||
}
|
||||
// end
|
||||
// output the code point for digit q
|
||||
$output .= self::digit_to_char($q);
|
||||
// let bias = adapt(delta, h + 1, test h equals b?)
|
||||
$bias = self::adapt($delta, $h + 1, $h === $b);
|
||||
// let delta = 0
|
||||
$delta = 0;
|
||||
// increment h
|
||||
$h++;
|
||||
}
|
||||
// end
|
||||
}
|
||||
// end
|
||||
// increment delta and n
|
||||
$delta++;
|
||||
$n++;
|
||||
}
|
||||
// end
|
||||
return $output;
|
||||
}
|
||||
/**
|
||||
* Convert a digit to its respective character
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc3492#section-5
|
||||
*
|
||||
* @param int $digit Digit in the range 0-35
|
||||
* @return string Single character corresponding to digit
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception On invalid digit (`idna.invalid_digit`)
|
||||
*/
|
||||
protected static function digit_to_char($digit)
|
||||
{
|
||||
// @codeCoverageIgnoreStart
|
||||
// As far as I know, this never happens, but still good to be sure.
|
||||
if ($digit < 0 || $digit > 35) {
|
||||
throw new Exception(\sprintf('Invalid digit %d', $digit), 'idna.invalid_digit', $digit);
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
$digits = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||
return \substr($digits, $digit, 1);
|
||||
}
|
||||
/**
|
||||
* Adapt the bias
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc3492#section-6.1
|
||||
* @param int $delta
|
||||
* @param int $numpoints
|
||||
* @param bool $firsttime
|
||||
* @return int|float New bias
|
||||
*
|
||||
* function adapt(delta,numpoints,firsttime):
|
||||
*/
|
||||
protected static function adapt($delta, $numpoints, $firsttime)
|
||||
{
|
||||
// if firsttime then let delta = delta div damp
|
||||
if ($firsttime) {
|
||||
$delta = \floor($delta / self::BOOTSTRAP_DAMP);
|
||||
} else {
|
||||
// else let delta = delta div 2
|
||||
$delta = \floor($delta / 2);
|
||||
}
|
||||
// let delta = delta + (delta div numpoints)
|
||||
$delta += \floor($delta / $numpoints);
|
||||
// let k = 0
|
||||
$k = 0;
|
||||
// while delta > ((base - tmin) * tmax) div 2 do begin
|
||||
$max = \floor((self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN) * self::BOOTSTRAP_TMAX / 2);
|
||||
while ($delta > $max) {
|
||||
// let delta = delta div (base - tmin)
|
||||
$delta = \floor($delta / (self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN));
|
||||
// let k = k + base
|
||||
$k += self::BOOTSTRAP_BASE;
|
||||
}
|
||||
// end
|
||||
// return k + (((base - tmin + 1) * delta) div (delta + skew))
|
||||
return $k + \floor((self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN + 1) * $delta / ($delta + self::BOOTSTRAP_SKEW));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class to validate and to work with IPv6 addresses
|
||||
*
|
||||
* @package Requests\Utilities
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument;
|
||||
use VendorDuplicator\WpOrg\Requests\Utility\InputValidator;
|
||||
/**
|
||||
* Class to validate and to work with IPv6 addresses
|
||||
*
|
||||
* This was originally based on the PEAR class of the same name, but has been
|
||||
* entirely rewritten.
|
||||
*
|
||||
* @package Requests\Utilities
|
||||
*/
|
||||
final class Ipv6
|
||||
{
|
||||
/**
|
||||
* Uncompresses an IPv6 address
|
||||
*
|
||||
* RFC 4291 allows you to compress consecutive zero pieces in an address to
|
||||
* '::'. This method expects a valid IPv6 address and expands the '::' to
|
||||
* the required number of zero pieces.
|
||||
*
|
||||
* Example: FF01::101 -> FF01:0:0:0:0:0:0:101
|
||||
* ::1 -> 0:0:0:0:0:0:0:1
|
||||
*
|
||||
* @author Alexander Merz <alexander.merz@web.de>
|
||||
* @author elfrink at introweb dot nl
|
||||
* @author Josh Peck <jmp at joshpeck dot org>
|
||||
* @copyright 2003-2005 The PHP Group
|
||||
* @license https://opensource.org/licenses/bsd-license.php
|
||||
*
|
||||
* @param string|Stringable $ip An IPv6 address
|
||||
* @return string The uncompressed IPv6 address
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string or a stringable object.
|
||||
*/
|
||||
public static function uncompress($ip)
|
||||
{
|
||||
if (InputValidator::is_string_or_stringable($ip) === \false) {
|
||||
throw InvalidArgument::create(1, '$ip', 'string|Stringable', \gettype($ip));
|
||||
}
|
||||
$ip = (string) $ip;
|
||||
if (\substr_count($ip, '::') !== 1) {
|
||||
return $ip;
|
||||
}
|
||||
list($ip1, $ip2) = \explode('::', $ip);
|
||||
$c1 = $ip1 === '' ? -1 : \substr_count($ip1, ':');
|
||||
$c2 = $ip2 === '' ? -1 : \substr_count($ip2, ':');
|
||||
if (\strpos($ip2, '.') !== \false) {
|
||||
$c2++;
|
||||
}
|
||||
if ($c1 === -1 && $c2 === -1) {
|
||||
// ::
|
||||
$ip = '0:0:0:0:0:0:0:0';
|
||||
} elseif ($c1 === -1) {
|
||||
// ::xxx
|
||||
$fill = \str_repeat('0:', 7 - $c2);
|
||||
$ip = \str_replace('::', $fill, $ip);
|
||||
} elseif ($c2 === -1) {
|
||||
// xxx::
|
||||
$fill = \str_repeat(':0', 7 - $c1);
|
||||
$ip = \str_replace('::', $fill, $ip);
|
||||
} else {
|
||||
// xxx::xxx
|
||||
$fill = ':' . \str_repeat('0:', 6 - $c2 - $c1);
|
||||
$ip = \str_replace('::', $fill, $ip);
|
||||
}
|
||||
return $ip;
|
||||
}
|
||||
/**
|
||||
* Compresses an IPv6 address
|
||||
*
|
||||
* RFC 4291 allows you to compress consecutive zero pieces in an address to
|
||||
* '::'. This method expects a valid IPv6 address and compresses consecutive
|
||||
* zero pieces to '::'.
|
||||
*
|
||||
* Example: FF01:0:0:0:0:0:0:101 -> FF01::101
|
||||
* 0:0:0:0:0:0:0:1 -> ::1
|
||||
*
|
||||
* @see \VendorDuplicator\WpOrg\Requests\Ipv6::uncompress()
|
||||
*
|
||||
* @param string $ip An IPv6 address
|
||||
* @return string The compressed IPv6 address
|
||||
*/
|
||||
public static function compress($ip)
|
||||
{
|
||||
// Prepare the IP to be compressed.
|
||||
// Note: Input validation is handled in the `uncompress()` method, which is the first call made in this method.
|
||||
$ip = self::uncompress($ip);
|
||||
$ip_parts = self::split_v6_v4($ip);
|
||||
// Replace all leading zeros
|
||||
$ip_parts[0] = \preg_replace('/(^|:)0+([0-9])/', 'VendorDuplicator\\1\\2', $ip_parts[0]);
|
||||
// Find bunches of zeros
|
||||
if (\preg_match_all('/(?:^|:)(?:0(?::|$))+/', $ip_parts[0], $matches, \PREG_OFFSET_CAPTURE)) {
|
||||
$max = 0;
|
||||
$pos = null;
|
||||
foreach ($matches[0] as $match) {
|
||||
if (\strlen($match[0]) > $max) {
|
||||
$max = \strlen($match[0]);
|
||||
$pos = $match[1];
|
||||
}
|
||||
}
|
||||
$ip_parts[0] = \substr_replace($ip_parts[0], '::', $pos, $max);
|
||||
}
|
||||
if ($ip_parts[1] !== '') {
|
||||
return \implode(':', $ip_parts);
|
||||
} else {
|
||||
return $ip_parts[0];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Splits an IPv6 address into the IPv6 and IPv4 representation parts
|
||||
*
|
||||
* RFC 4291 allows you to represent the last two parts of an IPv6 address
|
||||
* using the standard IPv4 representation
|
||||
*
|
||||
* Example: 0:0:0:0:0:0:13.1.68.3
|
||||
* 0:0:0:0:0:FFFF:129.144.52.38
|
||||
*
|
||||
* @param string $ip An IPv6 address
|
||||
* @return string[] [0] contains the IPv6 represented part, and [1] the IPv4 represented part
|
||||
*/
|
||||
private static function split_v6_v4($ip)
|
||||
{
|
||||
if (\strpos($ip, '.') !== \false) {
|
||||
$pos = \strrpos($ip, ':');
|
||||
$ipv6_part = \substr($ip, 0, $pos);
|
||||
$ipv4_part = \substr($ip, $pos + 1);
|
||||
return [$ipv6_part, $ipv4_part];
|
||||
} else {
|
||||
return [$ip, ''];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Checks an IPv6 address
|
||||
*
|
||||
* Checks if the given IP is a valid IPv6 address
|
||||
*
|
||||
* @param string $ip An IPv6 address
|
||||
* @return bool true if $ip is a valid IPv6 address
|
||||
*/
|
||||
public static function check_ipv6($ip)
|
||||
{
|
||||
// Note: Input validation is handled in the `uncompress()` method, which is the first call made in this method.
|
||||
$ip = self::uncompress($ip);
|
||||
list($ipv6, $ipv4) = self::split_v6_v4($ip);
|
||||
$ipv6 = \explode(':', $ipv6);
|
||||
$ipv4 = \explode('.', $ipv4);
|
||||
if (\count($ipv6) === 8 && \count($ipv4) === 1 || \count($ipv6) === 6 && \count($ipv4) === 4) {
|
||||
foreach ($ipv6 as $ipv6_part) {
|
||||
// The section can't be empty
|
||||
if ($ipv6_part === '') {
|
||||
return \false;
|
||||
}
|
||||
// Nor can it be over four characters
|
||||
if (\strlen($ipv6_part) > 4) {
|
||||
return \false;
|
||||
}
|
||||
// Remove leading zeros (this is safe because of the above)
|
||||
$ipv6_part = \ltrim($ipv6_part, '0');
|
||||
if ($ipv6_part === '') {
|
||||
$ipv6_part = '0';
|
||||
}
|
||||
// Check the value is valid
|
||||
$value = \hexdec($ipv6_part);
|
||||
if (\dechex($value) !== \strtolower($ipv6_part) || $value < 0 || $value > 0xffff) {
|
||||
return \false;
|
||||
}
|
||||
}
|
||||
if (\count($ipv4) === 4) {
|
||||
foreach ($ipv4 as $ipv4_part) {
|
||||
$value = (int) $ipv4_part;
|
||||
if ((string) $value !== $ipv4_part || $value < 0 || $value > 0xff) {
|
||||
return \false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return \true;
|
||||
} else {
|
||||
return \false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,862 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* IRI parser/serialiser/normaliser
|
||||
*
|
||||
* @package Requests\Utilities
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception;
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument;
|
||||
use VendorDuplicator\WpOrg\Requests\Ipv6;
|
||||
use VendorDuplicator\WpOrg\Requests\Port;
|
||||
use VendorDuplicator\WpOrg\Requests\Utility\InputValidator;
|
||||
/**
|
||||
* IRI parser/serialiser/normaliser
|
||||
*
|
||||
* Copyright (c) 2007-2010, Geoffrey Sneddon and Steve Minutillo.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the SimplePie Team nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @package Requests\Utilities
|
||||
* @author Geoffrey Sneddon
|
||||
* @author Steve Minutillo
|
||||
* @copyright 2007-2009 Geoffrey Sneddon and Steve Minutillo
|
||||
* @license https://opensource.org/licenses/bsd-license.php
|
||||
* @link http://hg.gsnedders.com/iri/
|
||||
*
|
||||
* @property string $iri IRI we're working with
|
||||
* @property-read string $uri IRI in URI form, {@see \WpOrg\Requests\Iri::to_uri()}
|
||||
* @property string $scheme Scheme part of the IRI
|
||||
* @property string $authority Authority part, formatted for a URI (userinfo + host + port)
|
||||
* @property string $iauthority Authority part of the IRI (userinfo + host + port)
|
||||
* @property string $userinfo Userinfo part, formatted for a URI (after '://' and before '@')
|
||||
* @property string $iuserinfo Userinfo part of the IRI (after '://' and before '@')
|
||||
* @property string $host Host part, formatted for a URI
|
||||
* @property string $ihost Host part of the IRI
|
||||
* @property string $port Port part of the IRI (after ':')
|
||||
* @property string $path Path part, formatted for a URI (after first '/')
|
||||
* @property string $ipath Path part of the IRI (after first '/')
|
||||
* @property string $query Query part, formatted for a URI (after '?')
|
||||
* @property string $iquery Query part of the IRI (after '?')
|
||||
* @property string $fragment Fragment, formatted for a URI (after '#')
|
||||
* @property string $ifragment Fragment part of the IRI (after '#')
|
||||
*/
|
||||
class Iri
|
||||
{
|
||||
/**
|
||||
* Scheme
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $scheme = null;
|
||||
/**
|
||||
* User Information
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $iuserinfo = null;
|
||||
/**
|
||||
* ihost
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $ihost = null;
|
||||
/**
|
||||
* Port
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $port = null;
|
||||
/**
|
||||
* ipath
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $ipath = '';
|
||||
/**
|
||||
* iquery
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $iquery = null;
|
||||
/**
|
||||
* ifragment|null
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $ifragment = null;
|
||||
/**
|
||||
* Normalization database
|
||||
*
|
||||
* Each key is the scheme, each value is an array with each key as the IRI
|
||||
* part and value as the default value for that part.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $normalization = array('acap' => array('port' => Port::ACAP), 'dict' => array('port' => Port::DICT), 'file' => array('ihost' => 'localhost'), 'http' => array('port' => Port::HTTP), 'https' => array('port' => Port::HTTPS));
|
||||
/**
|
||||
* Return the entire IRI when you try and read the object as a string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->get_iri();
|
||||
}
|
||||
/**
|
||||
* Overload __set() to provide access via properties
|
||||
*
|
||||
* @param string $name Property name
|
||||
* @param mixed $value Property value
|
||||
*/
|
||||
public function __set($name, $value)
|
||||
{
|
||||
if (\method_exists($this, 'set_' . $name)) {
|
||||
\call_user_func(array($this, 'set_' . $name), $value);
|
||||
} elseif ($name === 'iauthority' || $name === 'iuserinfo' || $name === 'ihost' || $name === 'ipath' || $name === 'iquery' || $name === 'ifragment') {
|
||||
\call_user_func(array($this, 'set_' . \substr($name, 1)), $value);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Overload __get() to provide access via properties
|
||||
*
|
||||
* @param string $name Property name
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
// isset() returns false for null, we don't want to do that
|
||||
// Also why we use array_key_exists below instead of isset()
|
||||
$props = \get_object_vars($this);
|
||||
if ($name === 'iri' || $name === 'uri' || $name === 'iauthority' || $name === 'authority') {
|
||||
$method = 'get_' . $name;
|
||||
$return = $this->{$method}();
|
||||
} elseif (\array_key_exists($name, $props)) {
|
||||
$return = $this->{$name};
|
||||
} elseif (($prop = 'i' . $name) && \array_key_exists($prop, $props)) {
|
||||
$name = $prop;
|
||||
$return = $this->{$prop};
|
||||
} elseif (($prop = \substr($name, 1)) && \array_key_exists($prop, $props)) {
|
||||
$name = $prop;
|
||||
$return = $this->{$prop};
|
||||
} else {
|
||||
\trigger_error('Undefined property: ' . \get_class($this) . '::' . $name, \E_USER_NOTICE);
|
||||
$return = null;
|
||||
}
|
||||
if ($return === null && isset($this->normalization[$this->scheme][$name])) {
|
||||
return $this->normalization[$this->scheme][$name];
|
||||
} else {
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Overload __isset() to provide access via properties
|
||||
*
|
||||
* @param string $name Property name
|
||||
* @return bool
|
||||
*/
|
||||
public function __isset($name)
|
||||
{
|
||||
return \method_exists($this, 'get_' . $name) || isset($this->{$name});
|
||||
}
|
||||
/**
|
||||
* Overload __unset() to provide access via properties
|
||||
*
|
||||
* @param string $name Property name
|
||||
*/
|
||||
public function __unset($name)
|
||||
{
|
||||
if (\method_exists($this, 'set_' . $name)) {
|
||||
\call_user_func(array($this, 'set_' . $name), '');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Create a new IRI object, from a specified string
|
||||
*
|
||||
* @param string|Stringable|null $iri
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $iri argument is not a string, Stringable or null.
|
||||
*/
|
||||
public function __construct($iri = null)
|
||||
{
|
||||
if ($iri !== null && InputValidator::is_string_or_stringable($iri) === \false) {
|
||||
throw InvalidArgument::create(1, '$iri', 'string|Stringable|null', \gettype($iri));
|
||||
}
|
||||
$this->set_iri($iri);
|
||||
}
|
||||
/**
|
||||
* Create a new IRI object by resolving a relative IRI
|
||||
*
|
||||
* Returns false if $base is not absolute, otherwise an IRI.
|
||||
*
|
||||
* @param \VendorDuplicator\WpOrg\Requests\Iri|string $base (Absolute) Base IRI
|
||||
* @param \VendorDuplicator\WpOrg\Requests\Iri|string $relative Relative IRI
|
||||
* @return \VendorDuplicator\WpOrg\Requests\Iri|false
|
||||
*/
|
||||
public static function absolutize($base, $relative)
|
||||
{
|
||||
if (!$relative instanceof self) {
|
||||
$relative = new self($relative);
|
||||
}
|
||||
if (!$relative->is_valid()) {
|
||||
return \false;
|
||||
} elseif ($relative->scheme !== null) {
|
||||
return clone $relative;
|
||||
}
|
||||
if (!$base instanceof self) {
|
||||
$base = new self($base);
|
||||
}
|
||||
if ($base->scheme === null || !$base->is_valid()) {
|
||||
return \false;
|
||||
}
|
||||
if ($relative->get_iri() !== '') {
|
||||
if ($relative->iuserinfo !== null || $relative->ihost !== null || $relative->port !== null) {
|
||||
$target = clone $relative;
|
||||
$target->scheme = $base->scheme;
|
||||
} else {
|
||||
$target = new self();
|
||||
$target->scheme = $base->scheme;
|
||||
$target->iuserinfo = $base->iuserinfo;
|
||||
$target->ihost = $base->ihost;
|
||||
$target->port = $base->port;
|
||||
if ($relative->ipath !== '') {
|
||||
if ($relative->ipath[0] === '/') {
|
||||
$target->ipath = $relative->ipath;
|
||||
} elseif (($base->iuserinfo !== null || $base->ihost !== null || $base->port !== null) && $base->ipath === '') {
|
||||
$target->ipath = '/' . $relative->ipath;
|
||||
} elseif (($last_segment = \strrpos($base->ipath, '/')) !== \false) {
|
||||
$target->ipath = \substr($base->ipath, 0, $last_segment + 1) . $relative->ipath;
|
||||
} else {
|
||||
$target->ipath = $relative->ipath;
|
||||
}
|
||||
$target->ipath = $target->remove_dot_segments($target->ipath);
|
||||
$target->iquery = $relative->iquery;
|
||||
} else {
|
||||
$target->ipath = $base->ipath;
|
||||
if ($relative->iquery !== null) {
|
||||
$target->iquery = $relative->iquery;
|
||||
} elseif ($base->iquery !== null) {
|
||||
$target->iquery = $base->iquery;
|
||||
}
|
||||
}
|
||||
$target->ifragment = $relative->ifragment;
|
||||
}
|
||||
} else {
|
||||
$target = clone $base;
|
||||
$target->ifragment = null;
|
||||
}
|
||||
$target->scheme_normalization();
|
||||
return $target;
|
||||
}
|
||||
/**
|
||||
* Parse an IRI into scheme/authority/path/query/fragment segments
|
||||
*
|
||||
* @param string $iri
|
||||
* @return array
|
||||
*/
|
||||
protected function parse_iri($iri)
|
||||
{
|
||||
$iri = \trim($iri, " \t\n\f\r");
|
||||
$has_match = \preg_match('/^((?P<scheme>[^:\\/?#]+):)?(\\/\\/(?P<authority>[^\\/?#]*))?(?P<path>[^?#]*)(\\?(?P<query>[^#]*))?(#(?P<fragment>.*))?$/', $iri, $match);
|
||||
if (!$has_match) {
|
||||
throw new Exception('Cannot parse supplied IRI', 'iri.cannot_parse', $iri);
|
||||
}
|
||||
if ($match[1] === '') {
|
||||
$match['scheme'] = null;
|
||||
}
|
||||
if (!isset($match[3]) || $match[3] === '') {
|
||||
$match['authority'] = null;
|
||||
}
|
||||
if (!isset($match[5])) {
|
||||
$match['path'] = '';
|
||||
}
|
||||
if (!isset($match[6]) || $match[6] === '') {
|
||||
$match['query'] = null;
|
||||
}
|
||||
if (!isset($match[8]) || $match[8] === '') {
|
||||
$match['fragment'] = null;
|
||||
}
|
||||
return $match;
|
||||
}
|
||||
/**
|
||||
* Remove dot segments from a path
|
||||
*
|
||||
* @param string $input
|
||||
* @return string
|
||||
*/
|
||||
protected function remove_dot_segments($input)
|
||||
{
|
||||
$output = '';
|
||||
while (\strpos($input, './') !== \false || \strpos($input, '/.') !== \false || $input === '.' || $input === '..') {
|
||||
// A: If the input buffer begins with a prefix of "../" or "./",
|
||||
// then remove that prefix from the input buffer; otherwise,
|
||||
if (\strpos($input, '../') === 0) {
|
||||
$input = \substr($input, 3);
|
||||
} elseif (\strpos($input, './') === 0) {
|
||||
$input = \substr($input, 2);
|
||||
} elseif (\strpos($input, '/./') === 0) {
|
||||
$input = \substr($input, 2);
|
||||
} elseif ($input === '/.') {
|
||||
$input = '/';
|
||||
} elseif (\strpos($input, '/../') === 0) {
|
||||
$input = \substr($input, 3);
|
||||
$output = \substr_replace($output, '', \strrpos($output, '/') ?: 0);
|
||||
} elseif ($input === '/..') {
|
||||
$input = '/';
|
||||
$output = \substr_replace($output, '', \strrpos($output, '/') ?: 0);
|
||||
} elseif ($input === '.' || $input === '..') {
|
||||
$input = '';
|
||||
} elseif (($pos = \strpos($input, '/', 1)) !== \false) {
|
||||
$output .= \substr($input, 0, $pos);
|
||||
$input = \substr_replace($input, '', 0, $pos);
|
||||
} else {
|
||||
$output .= $input;
|
||||
$input = '';
|
||||
}
|
||||
}
|
||||
return $output . $input;
|
||||
}
|
||||
/**
|
||||
* Replace invalid character with percent encoding
|
||||
*
|
||||
* @param string $text Input string
|
||||
* @param string $extra_chars Valid characters not in iunreserved or
|
||||
* iprivate (this is ASCII-only)
|
||||
* @param bool $iprivate Allow iprivate
|
||||
* @return string
|
||||
*/
|
||||
protected function replace_invalid_with_pct_encoding($text, $extra_chars, $iprivate = \false)
|
||||
{
|
||||
// Normalize as many pct-encoded sections as possible
|
||||
$text = \preg_replace_callback('/(?:%[A-Fa-f0-9]{2})+/', array($this, 'remove_iunreserved_percent_encoded'), $text);
|
||||
// Replace invalid percent characters
|
||||
$text = \preg_replace('/%(?![A-Fa-f0-9]{2})/', '%25', $text);
|
||||
// Add unreserved and % to $extra_chars (the latter is safe because all
|
||||
// pct-encoded sections are now valid).
|
||||
$extra_chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~%';
|
||||
// Now replace any bytes that aren't allowed with their pct-encoded versions
|
||||
$position = 0;
|
||||
$strlen = \strlen($text);
|
||||
while (($position += \strspn($text, $extra_chars, $position)) < $strlen) {
|
||||
$value = \ord($text[$position]);
|
||||
// Start position
|
||||
$start = $position;
|
||||
// By default we are valid
|
||||
$valid = \true;
|
||||
// No one byte sequences are valid due to the while.
|
||||
// Two byte sequence:
|
||||
if (($value & 0xe0) === 0xc0) {
|
||||
$character = ($value & 0x1f) << 6;
|
||||
$length = 2;
|
||||
$remaining = 1;
|
||||
} elseif (($value & 0xf0) === 0xe0) {
|
||||
$character = ($value & 0xf) << 12;
|
||||
$length = 3;
|
||||
$remaining = 2;
|
||||
} elseif (($value & 0xf8) === 0xf0) {
|
||||
$character = ($value & 0x7) << 18;
|
||||
$length = 4;
|
||||
$remaining = 3;
|
||||
} else {
|
||||
$valid = \false;
|
||||
$length = 1;
|
||||
$remaining = 0;
|
||||
}
|
||||
if ($remaining) {
|
||||
if ($position + $length <= $strlen) {
|
||||
for ($position++; $remaining; $position++) {
|
||||
$value = \ord($text[$position]);
|
||||
// Check that the byte is valid, then add it to the character:
|
||||
if (($value & 0xc0) === 0x80) {
|
||||
$character |= ($value & 0x3f) << --$remaining * 6;
|
||||
} else {
|
||||
$valid = \false;
|
||||
$position--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$position = $strlen - 1;
|
||||
$valid = \false;
|
||||
}
|
||||
}
|
||||
// Percent encode anything invalid or not in ucschar
|
||||
if (!$valid || $length > 1 && $character <= 0x7f || $length > 2 && $character <= 0x7ff || $length > 3 && $character <= 0xffff || ($character & 0xfffe) === 0xfffe || $character >= 0xfdd0 && $character <= 0xfdef || ($character > 0xd7ff && $character < 0xf900 || $character < 0xa0 || $character > 0xefffd) && (!$iprivate || $character < 0xe000 || $character > 0x10fffd)) {
|
||||
// If we were a character, pretend we weren't, but rather an error.
|
||||
if ($valid) {
|
||||
$position--;
|
||||
}
|
||||
for ($j = $start; $j <= $position; $j++) {
|
||||
$text = \substr_replace($text, \sprintf('%%%02X', \ord($text[$j])), $j, 1);
|
||||
$j += 2;
|
||||
$position += 2;
|
||||
$strlen += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
/**
|
||||
* Callback function for preg_replace_callback.
|
||||
*
|
||||
* Removes sequences of percent encoded bytes that represent UTF-8
|
||||
* encoded characters in iunreserved
|
||||
*
|
||||
* @param array $regex_match PCRE match
|
||||
* @return string Replacement
|
||||
*/
|
||||
protected function remove_iunreserved_percent_encoded($regex_match)
|
||||
{
|
||||
// As we just have valid percent encoded sequences we can just explode
|
||||
// and ignore the first member of the returned array (an empty string).
|
||||
$bytes = \explode('%', $regex_match[0]);
|
||||
// Initialize the new string (this is what will be returned) and that
|
||||
// there are no bytes remaining in the current sequence (unsurprising
|
||||
// at the first byte!).
|
||||
$string = '';
|
||||
$remaining = 0;
|
||||
// Loop over each and every byte, and set $value to its value
|
||||
for ($i = 1, $len = \count($bytes); $i < $len; $i++) {
|
||||
$value = \hexdec($bytes[$i]);
|
||||
// If we're the first byte of sequence:
|
||||
if (!$remaining) {
|
||||
// Start position
|
||||
$start = $i;
|
||||
// By default we are valid
|
||||
$valid = \true;
|
||||
// One byte sequence:
|
||||
if ($value <= 0x7f) {
|
||||
$character = $value;
|
||||
$length = 1;
|
||||
} elseif (($value & 0xe0) === 0xc0) {
|
||||
$character = ($value & 0x1f) << 6;
|
||||
$length = 2;
|
||||
$remaining = 1;
|
||||
} elseif (($value & 0xf0) === 0xe0) {
|
||||
$character = ($value & 0xf) << 12;
|
||||
$length = 3;
|
||||
$remaining = 2;
|
||||
} elseif (($value & 0xf8) === 0xf0) {
|
||||
$character = ($value & 0x7) << 18;
|
||||
$length = 4;
|
||||
$remaining = 3;
|
||||
} else {
|
||||
$valid = \false;
|
||||
$remaining = 0;
|
||||
}
|
||||
} else {
|
||||
// Check that the byte is valid, then add it to the character:
|
||||
if (($value & 0xc0) === 0x80) {
|
||||
$remaining--;
|
||||
$character |= ($value & 0x3f) << $remaining * 6;
|
||||
} else {
|
||||
$valid = \false;
|
||||
$remaining = 0;
|
||||
$i--;
|
||||
}
|
||||
}
|
||||
// If we've reached the end of the current byte sequence, append it to Unicode::$data
|
||||
if (!$remaining) {
|
||||
// Percent encode anything invalid or not in iunreserved
|
||||
if (!$valid || $length > 1 && $character <= 0x7f || $length > 2 && $character <= 0x7ff || $length > 3 && $character <= 0xffff || $character < 0x2d || $character > 0xefffd || ($character & 0xfffe) === 0xfffe || $character >= 0xfdd0 && $character <= 0xfdef || $character === 0x2f || $character > 0x39 && $character < 0x41 || $character > 0x5a && $character < 0x61 || $character > 0x7a && $character < 0x7e || $character > 0x7e && $character < 0xa0 || $character > 0xd7ff && $character < 0xf900) {
|
||||
for ($j = $start; $j <= $i; $j++) {
|
||||
$string .= '%' . \strtoupper($bytes[$j]);
|
||||
}
|
||||
} else {
|
||||
for ($j = $start; $j <= $i; $j++) {
|
||||
$string .= \chr(\hexdec($bytes[$j]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we have any bytes left over they are invalid (i.e., we are
|
||||
// mid-way through a multi-byte sequence)
|
||||
if ($remaining) {
|
||||
for ($j = $start; $j < $len; $j++) {
|
||||
$string .= '%' . \strtoupper($bytes[$j]);
|
||||
}
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
protected function scheme_normalization()
|
||||
{
|
||||
if (isset($this->normalization[$this->scheme]['iuserinfo']) && $this->iuserinfo === $this->normalization[$this->scheme]['iuserinfo']) {
|
||||
$this->iuserinfo = null;
|
||||
}
|
||||
if (isset($this->normalization[$this->scheme]['ihost']) && $this->ihost === $this->normalization[$this->scheme]['ihost']) {
|
||||
$this->ihost = null;
|
||||
}
|
||||
if (isset($this->normalization[$this->scheme]['port']) && $this->port === $this->normalization[$this->scheme]['port']) {
|
||||
$this->port = null;
|
||||
}
|
||||
if (isset($this->normalization[$this->scheme]['ipath']) && $this->ipath === $this->normalization[$this->scheme]['ipath']) {
|
||||
$this->ipath = '';
|
||||
}
|
||||
if (isset($this->ihost) && empty($this->ipath)) {
|
||||
$this->ipath = '/';
|
||||
}
|
||||
if (isset($this->normalization[$this->scheme]['iquery']) && $this->iquery === $this->normalization[$this->scheme]['iquery']) {
|
||||
$this->iquery = null;
|
||||
}
|
||||
if (isset($this->normalization[$this->scheme]['ifragment']) && $this->ifragment === $this->normalization[$this->scheme]['ifragment']) {
|
||||
$this->ifragment = null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Check if the object represents a valid IRI. This needs to be done on each
|
||||
* call as some things change depending on another part of the IRI.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_valid()
|
||||
{
|
||||
$isauthority = $this->iuserinfo !== null || $this->ihost !== null || $this->port !== null;
|
||||
if ($this->ipath !== '' && ($isauthority && $this->ipath[0] !== '/' || $this->scheme === null && !$isauthority && \strpos($this->ipath, ':') !== \false && (\strpos($this->ipath, '/') === \false ? \true : \strpos($this->ipath, ':') < \strpos($this->ipath, '/')))) {
|
||||
return \false;
|
||||
}
|
||||
return \true;
|
||||
}
|
||||
/**
|
||||
* Set the entire IRI. Returns true on success, false on failure (if there
|
||||
* are any invalid characters).
|
||||
*
|
||||
* @param string $iri
|
||||
* @return bool
|
||||
*/
|
||||
protected function set_iri($iri)
|
||||
{
|
||||
static $cache;
|
||||
if (!$cache) {
|
||||
$cache = array();
|
||||
}
|
||||
if ($iri === null) {
|
||||
return \true;
|
||||
}
|
||||
$iri = (string) $iri;
|
||||
if (isset($cache[$iri])) {
|
||||
list($this->scheme, $this->iuserinfo, $this->ihost, $this->port, $this->ipath, $this->iquery, $this->ifragment, $return) = $cache[$iri];
|
||||
return $return;
|
||||
}
|
||||
$parsed = $this->parse_iri($iri);
|
||||
$return = $this->set_scheme($parsed['scheme']) && $this->set_authority($parsed['authority']) && $this->set_path($parsed['path']) && $this->set_query($parsed['query']) && $this->set_fragment($parsed['fragment']);
|
||||
$cache[$iri] = array($this->scheme, $this->iuserinfo, $this->ihost, $this->port, $this->ipath, $this->iquery, $this->ifragment, $return);
|
||||
return $return;
|
||||
}
|
||||
/**
|
||||
* Set the scheme. Returns true on success, false on failure (if there are
|
||||
* any invalid characters).
|
||||
*
|
||||
* @param string $scheme
|
||||
* @return bool
|
||||
*/
|
||||
protected function set_scheme($scheme)
|
||||
{
|
||||
if ($scheme === null) {
|
||||
$this->scheme = null;
|
||||
} elseif (!\preg_match('/^[A-Za-z][0-9A-Za-z+\\-.]*$/', $scheme)) {
|
||||
$this->scheme = null;
|
||||
return \false;
|
||||
} else {
|
||||
$this->scheme = \strtolower($scheme);
|
||||
}
|
||||
return \true;
|
||||
}
|
||||
/**
|
||||
* Set the authority. Returns true on success, false on failure (if there are
|
||||
* any invalid characters).
|
||||
*
|
||||
* @param string $authority
|
||||
* @return bool
|
||||
*/
|
||||
protected function set_authority($authority)
|
||||
{
|
||||
static $cache;
|
||||
if (!$cache) {
|
||||
$cache = array();
|
||||
}
|
||||
if ($authority === null) {
|
||||
$this->iuserinfo = null;
|
||||
$this->ihost = null;
|
||||
$this->port = null;
|
||||
return \true;
|
||||
}
|
||||
if (isset($cache[$authority])) {
|
||||
list($this->iuserinfo, $this->ihost, $this->port, $return) = $cache[$authority];
|
||||
return $return;
|
||||
}
|
||||
$remaining = $authority;
|
||||
if (($iuserinfo_end = \strrpos($remaining, '@')) !== \false) {
|
||||
$iuserinfo = \substr($remaining, 0, $iuserinfo_end);
|
||||
$remaining = \substr($remaining, $iuserinfo_end + 1);
|
||||
} else {
|
||||
$iuserinfo = null;
|
||||
}
|
||||
if (($port_start = \strpos($remaining, ':', \strpos($remaining, ']') ?: 0)) !== \false) {
|
||||
$port = \substr($remaining, $port_start + 1);
|
||||
if ($port === \false || $port === '') {
|
||||
$port = null;
|
||||
}
|
||||
$remaining = \substr($remaining, 0, $port_start);
|
||||
} else {
|
||||
$port = null;
|
||||
}
|
||||
$return = $this->set_userinfo($iuserinfo) && $this->set_host($remaining) && $this->set_port($port);
|
||||
$cache[$authority] = array($this->iuserinfo, $this->ihost, $this->port, $return);
|
||||
return $return;
|
||||
}
|
||||
/**
|
||||
* Set the iuserinfo.
|
||||
*
|
||||
* @param string $iuserinfo
|
||||
* @return bool
|
||||
*/
|
||||
protected function set_userinfo($iuserinfo)
|
||||
{
|
||||
if ($iuserinfo === null) {
|
||||
$this->iuserinfo = null;
|
||||
} else {
|
||||
$this->iuserinfo = $this->replace_invalid_with_pct_encoding($iuserinfo, '!$&\'()*+,;=:');
|
||||
$this->scheme_normalization();
|
||||
}
|
||||
return \true;
|
||||
}
|
||||
/**
|
||||
* Set the ihost. Returns true on success, false on failure (if there are
|
||||
* any invalid characters).
|
||||
*
|
||||
* @param string $ihost
|
||||
* @return bool
|
||||
*/
|
||||
protected function set_host($ihost)
|
||||
{
|
||||
if ($ihost === null) {
|
||||
$this->ihost = null;
|
||||
return \true;
|
||||
}
|
||||
if (\substr($ihost, 0, 1) === '[' && \substr($ihost, -1) === ']') {
|
||||
if (Ipv6::check_ipv6(\substr($ihost, 1, -1))) {
|
||||
$this->ihost = '[' . Ipv6::compress(\substr($ihost, 1, -1)) . ']';
|
||||
} else {
|
||||
$this->ihost = null;
|
||||
return \false;
|
||||
}
|
||||
} else {
|
||||
$ihost = $this->replace_invalid_with_pct_encoding($ihost, '!$&\'()*+,;=');
|
||||
// Lowercase, but ignore pct-encoded sections (as they should
|
||||
// remain uppercase). This must be done after the previous step
|
||||
// as that can add unescaped characters.
|
||||
$position = 0;
|
||||
$strlen = \strlen($ihost);
|
||||
while (($position += \strcspn($ihost, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ%', $position)) < $strlen) {
|
||||
if ($ihost[$position] === '%') {
|
||||
$position += 3;
|
||||
} else {
|
||||
$ihost[$position] = \strtolower($ihost[$position]);
|
||||
$position++;
|
||||
}
|
||||
}
|
||||
$this->ihost = $ihost;
|
||||
}
|
||||
$this->scheme_normalization();
|
||||
return \true;
|
||||
}
|
||||
/**
|
||||
* Set the port. Returns true on success, false on failure (if there are
|
||||
* any invalid characters).
|
||||
*
|
||||
* @param string $port
|
||||
* @return bool
|
||||
*/
|
||||
protected function set_port($port)
|
||||
{
|
||||
if ($port === null) {
|
||||
$this->port = null;
|
||||
return \true;
|
||||
}
|
||||
if (\strspn($port, '0123456789') === \strlen($port)) {
|
||||
$this->port = (int) $port;
|
||||
$this->scheme_normalization();
|
||||
return \true;
|
||||
}
|
||||
$this->port = null;
|
||||
return \false;
|
||||
}
|
||||
/**
|
||||
* Set the ipath.
|
||||
*
|
||||
* @param string $ipath
|
||||
* @return bool
|
||||
*/
|
||||
protected function set_path($ipath)
|
||||
{
|
||||
static $cache;
|
||||
if (!$cache) {
|
||||
$cache = array();
|
||||
}
|
||||
$ipath = (string) $ipath;
|
||||
if (isset($cache[$ipath])) {
|
||||
$this->ipath = $cache[$ipath][(int) ($this->scheme !== null)];
|
||||
} else {
|
||||
$valid = $this->replace_invalid_with_pct_encoding($ipath, '!$&\'()*+,;=@:/');
|
||||
$removed = $this->remove_dot_segments($valid);
|
||||
$cache[$ipath] = array($valid, $removed);
|
||||
$this->ipath = $this->scheme !== null ? $removed : $valid;
|
||||
}
|
||||
$this->scheme_normalization();
|
||||
return \true;
|
||||
}
|
||||
/**
|
||||
* Set the iquery.
|
||||
*
|
||||
* @param string $iquery
|
||||
* @return bool
|
||||
*/
|
||||
protected function set_query($iquery)
|
||||
{
|
||||
if ($iquery === null) {
|
||||
$this->iquery = null;
|
||||
} else {
|
||||
$this->iquery = $this->replace_invalid_with_pct_encoding($iquery, '!$&\'()*+,;=:@/?', \true);
|
||||
$this->scheme_normalization();
|
||||
}
|
||||
return \true;
|
||||
}
|
||||
/**
|
||||
* Set the ifragment.
|
||||
*
|
||||
* @param string $ifragment
|
||||
* @return bool
|
||||
*/
|
||||
protected function set_fragment($ifragment)
|
||||
{
|
||||
if ($ifragment === null) {
|
||||
$this->ifragment = null;
|
||||
} else {
|
||||
$this->ifragment = $this->replace_invalid_with_pct_encoding($ifragment, '!$&\'()*+,;=:@/?');
|
||||
$this->scheme_normalization();
|
||||
}
|
||||
return \true;
|
||||
}
|
||||
/**
|
||||
* Convert an IRI to a URI (or parts thereof)
|
||||
*
|
||||
* @param string|bool $iri IRI to convert (or false from {@see \WpOrg\Requests\Iri::get_iri()})
|
||||
* @return string|false URI if IRI is valid, false otherwise.
|
||||
*/
|
||||
protected function to_uri($iri)
|
||||
{
|
||||
if (!\is_string($iri)) {
|
||||
return \false;
|
||||
}
|
||||
static $non_ascii;
|
||||
if (!$non_ascii) {
|
||||
$non_ascii = \implode('', \range("\x80", "\xff"));
|
||||
}
|
||||
$position = 0;
|
||||
$strlen = \strlen($iri);
|
||||
while (($position += \strcspn($iri, $non_ascii, $position)) < $strlen) {
|
||||
$iri = \substr_replace($iri, \sprintf('%%%02X', \ord($iri[$position])), $position, 1);
|
||||
$position += 3;
|
||||
$strlen += 2;
|
||||
}
|
||||
return $iri;
|
||||
}
|
||||
/**
|
||||
* Get the complete IRI
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
protected function get_iri()
|
||||
{
|
||||
if (!$this->is_valid()) {
|
||||
return \false;
|
||||
}
|
||||
$iri = '';
|
||||
if ($this->scheme !== null) {
|
||||
$iri .= $this->scheme . ':';
|
||||
}
|
||||
if (($iauthority = $this->get_iauthority()) !== null) {
|
||||
$iri .= '//' . $iauthority;
|
||||
}
|
||||
$iri .= $this->ipath;
|
||||
if ($this->iquery !== null) {
|
||||
$iri .= '?' . $this->iquery;
|
||||
}
|
||||
if ($this->ifragment !== null) {
|
||||
$iri .= '#' . $this->ifragment;
|
||||
}
|
||||
return $iri;
|
||||
}
|
||||
/**
|
||||
* Get the complete URI
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_uri()
|
||||
{
|
||||
return $this->to_uri($this->get_iri());
|
||||
}
|
||||
/**
|
||||
* Get the complete iauthority
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function get_iauthority()
|
||||
{
|
||||
if ($this->iuserinfo === null && $this->ihost === null && $this->port === null) {
|
||||
return null;
|
||||
}
|
||||
$iauthority = '';
|
||||
if ($this->iuserinfo !== null) {
|
||||
$iauthority .= $this->iuserinfo . '@';
|
||||
}
|
||||
if ($this->ihost !== null) {
|
||||
$iauthority .= $this->ihost;
|
||||
}
|
||||
if ($this->port !== null) {
|
||||
$iauthority .= ':' . $this->port;
|
||||
}
|
||||
return $iauthority;
|
||||
}
|
||||
/**
|
||||
* Get the complete authority
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_authority()
|
||||
{
|
||||
$iauthority = $this->get_iauthority();
|
||||
if (\is_string($iauthority)) {
|
||||
return $this->to_uri($iauthority);
|
||||
} else {
|
||||
return $iauthority;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Port utilities for Requests
|
||||
*
|
||||
* @package Requests\Utilities
|
||||
* @since 2.0.0
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception;
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument;
|
||||
/**
|
||||
* Find the correct port depending on the Request type.
|
||||
*
|
||||
* @package Requests\Utilities
|
||||
* @since 2.0.0
|
||||
*/
|
||||
final class Port
|
||||
{
|
||||
/**
|
||||
* Port to use with Acap requests.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const ACAP = 674;
|
||||
/**
|
||||
* Port to use with Dictionary requests.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const DICT = 2628;
|
||||
/**
|
||||
* Port to use with HTTP requests.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const HTTP = 80;
|
||||
/**
|
||||
* Port to use with HTTP over SSL requests.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const HTTPS = 443;
|
||||
/**
|
||||
* Retrieve the port number to use.
|
||||
*
|
||||
* @param string $type Request type.
|
||||
* The following requests types are supported:
|
||||
* 'acap', 'dict', 'http' and 'https'.
|
||||
*
|
||||
* @return int
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When a non-string input has been passed.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception When a non-supported port is requested ('portnotsupported').
|
||||
*/
|
||||
public static function get($type)
|
||||
{
|
||||
if (!\is_string($type)) {
|
||||
throw InvalidArgument::create(1, '$type', 'string', \gettype($type));
|
||||
}
|
||||
$type = \strtoupper($type);
|
||||
if (!\defined("self::{$type}")) {
|
||||
$message = \sprintf('Invalid port type (%s) passed', $type);
|
||||
throw new Exception($message, 'portnotsupported');
|
||||
}
|
||||
return \constant("self::{$type}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy connection interface
|
||||
*
|
||||
* @package Requests\Proxy
|
||||
* @since 1.6
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Hooks;
|
||||
/**
|
||||
* Proxy connection interface
|
||||
*
|
||||
* Implement this interface to handle proxy settings and authentication
|
||||
*
|
||||
* Parameters should be passed via the constructor where possible, as this
|
||||
* makes it much easier for users to use your provider.
|
||||
*
|
||||
* @see \VendorDuplicator\WpOrg\Requests\Hooks
|
||||
*
|
||||
* @package Requests\Proxy
|
||||
* @since 1.6
|
||||
*/
|
||||
interface Proxy
|
||||
{
|
||||
/**
|
||||
* Register hooks as needed
|
||||
*
|
||||
* This method is called in {@see \WpOrg\Requests\Requests::request()} when the user
|
||||
* has set an instance as the 'auth' option. Use this callback to register all the
|
||||
* hooks you'll need.
|
||||
*
|
||||
* @see \VendorDuplicator\WpOrg\Requests\Hooks::register()
|
||||
* @param \VendorDuplicator\WpOrg\Requests\Hooks $hooks Hook system
|
||||
*/
|
||||
public function register(Hooks $hooks);
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* HTTP Proxy connection interface
|
||||
*
|
||||
* @package Requests\Proxy
|
||||
* @since 1.6
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Proxy;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\ArgumentCount;
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument;
|
||||
use VendorDuplicator\WpOrg\Requests\Hooks;
|
||||
use VendorDuplicator\WpOrg\Requests\Proxy;
|
||||
/**
|
||||
* HTTP Proxy connection interface
|
||||
*
|
||||
* Provides a handler for connection via an HTTP proxy
|
||||
*
|
||||
* @package Requests\Proxy
|
||||
* @since 1.6
|
||||
*/
|
||||
final class Http implements Proxy
|
||||
{
|
||||
/**
|
||||
* Proxy host and port
|
||||
*
|
||||
* Notation: "host:port" (eg 127.0.0.1:8080 or someproxy.com:3128)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $proxy;
|
||||
/**
|
||||
* Username
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $user;
|
||||
/**
|
||||
* Password
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $pass;
|
||||
/**
|
||||
* Do we need to authenticate? (ie username & password have been provided)
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $use_authentication;
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @since 1.6
|
||||
*
|
||||
* @param array|string|null $args Proxy as a string or an array of proxy, user and password.
|
||||
* When passed as an array, must have exactly one (proxy)
|
||||
* or three elements (proxy, user, password).
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed argument is not an array, a string or null.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\ArgumentCount On incorrect number of arguments (`proxyhttpbadargs`)
|
||||
*/
|
||||
public function __construct($args = null)
|
||||
{
|
||||
if (\is_string($args)) {
|
||||
$this->proxy = $args;
|
||||
} elseif (\is_array($args)) {
|
||||
if (\count($args) === 1) {
|
||||
list($this->proxy) = $args;
|
||||
} elseif (\count($args) === 3) {
|
||||
list($this->proxy, $this->user, $this->pass) = $args;
|
||||
$this->use_authentication = \true;
|
||||
} else {
|
||||
throw ArgumentCount::create('an array with exactly one element or exactly three elements', \count($args), 'proxyhttpbadargs');
|
||||
}
|
||||
} elseif ($args !== null) {
|
||||
throw InvalidArgument::create(1, '$args', 'array|string|null', \gettype($args));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Register the necessary callbacks
|
||||
*
|
||||
* @since 1.6
|
||||
* @see \VendorDuplicator\WpOrg\Requests\Proxy\Http::curl_before_send()
|
||||
* @see \VendorDuplicator\WpOrg\Requests\Proxy\Http::fsockopen_remote_socket()
|
||||
* @see \VendorDuplicator\WpOrg\Requests\Proxy\Http::fsockopen_remote_host_path()
|
||||
* @see \VendorDuplicator\WpOrg\Requests\Proxy\Http::fsockopen_header()
|
||||
* @param \VendorDuplicator\WpOrg\Requests\Hooks $hooks Hook system
|
||||
*/
|
||||
public function register(Hooks $hooks)
|
||||
{
|
||||
$hooks->register('curl.before_send', [$this, 'curl_before_send']);
|
||||
$hooks->register('fsockopen.remote_socket', [$this, 'fsockopen_remote_socket']);
|
||||
$hooks->register('fsockopen.remote_host_path', [$this, 'fsockopen_remote_host_path']);
|
||||
if ($this->use_authentication) {
|
||||
$hooks->register('fsockopen.after_headers', [$this, 'fsockopen_header']);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Set cURL parameters before the data is sent
|
||||
*
|
||||
* @since 1.6
|
||||
* @param resource|\CurlHandle $handle cURL handle
|
||||
*/
|
||||
public function curl_before_send(&$handle)
|
||||
{
|
||||
\curl_setopt($handle, \CURLOPT_PROXYTYPE, \CURLPROXY_HTTP);
|
||||
\curl_setopt($handle, \CURLOPT_PROXY, $this->proxy);
|
||||
if ($this->use_authentication) {
|
||||
\curl_setopt($handle, \CURLOPT_PROXYAUTH, \CURLAUTH_ANY);
|
||||
\curl_setopt($handle, \CURLOPT_PROXYUSERPWD, $this->get_auth_string());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Alter remote socket information before opening socket connection
|
||||
*
|
||||
* @since 1.6
|
||||
* @param string $remote_socket Socket connection string
|
||||
*/
|
||||
public function fsockopen_remote_socket(&$remote_socket)
|
||||
{
|
||||
$remote_socket = $this->proxy;
|
||||
}
|
||||
/**
|
||||
* Alter remote path before getting stream data
|
||||
*
|
||||
* @since 1.6
|
||||
* @param string $path Path to send in HTTP request string ("GET ...")
|
||||
* @param string $url Full URL we're requesting
|
||||
*/
|
||||
public function fsockopen_remote_host_path(&$path, $url)
|
||||
{
|
||||
$path = $url;
|
||||
}
|
||||
/**
|
||||
* Add extra headers to the request before sending
|
||||
*
|
||||
* @since 1.6
|
||||
* @param string $out HTTP header string
|
||||
*/
|
||||
public function fsockopen_header(&$out)
|
||||
{
|
||||
$out .= \sprintf("Proxy-Authorization: Basic %s\r\n", \base64_encode($this->get_auth_string()));
|
||||
}
|
||||
/**
|
||||
* Get the authentication string (user:pass)
|
||||
*
|
||||
* @since 1.6
|
||||
* @return string
|
||||
*/
|
||||
public function get_auth_string()
|
||||
{
|
||||
return $this->user . ':' . $this->pass;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,963 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Requests for PHP
|
||||
*
|
||||
* Inspired by Requests for Python.
|
||||
*
|
||||
* Based on concepts from SimplePie_File, RequestCore and WP_Http.
|
||||
*
|
||||
* @package Requests
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Auth\Basic;
|
||||
use VendorDuplicator\WpOrg\Requests\Capability;
|
||||
use VendorDuplicator\WpOrg\Requests\Cookie\Jar;
|
||||
use VendorDuplicator\WpOrg\Requests\Exception;
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument;
|
||||
use VendorDuplicator\WpOrg\Requests\Hooks;
|
||||
use VendorDuplicator\WpOrg\Requests\IdnaEncoder;
|
||||
use VendorDuplicator\WpOrg\Requests\Iri;
|
||||
use VendorDuplicator\WpOrg\Requests\Proxy\Http;
|
||||
use VendorDuplicator\WpOrg\Requests\Response;
|
||||
use VendorDuplicator\WpOrg\Requests\Transport\Curl;
|
||||
use VendorDuplicator\WpOrg\Requests\Transport\Fsockopen;
|
||||
use VendorDuplicator\WpOrg\Requests\Utility\InputValidator;
|
||||
/**
|
||||
* Requests for PHP
|
||||
*
|
||||
* Inspired by Requests for Python.
|
||||
*
|
||||
* Based on concepts from SimplePie_File, RequestCore and WP_Http.
|
||||
*
|
||||
* @package Requests
|
||||
*/
|
||||
class Requests
|
||||
{
|
||||
/**
|
||||
* POST method
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const POST = 'POST';
|
||||
/**
|
||||
* PUT method
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PUT = 'PUT';
|
||||
/**
|
||||
* GET method
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const GET = 'GET';
|
||||
/**
|
||||
* HEAD method
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HEAD = 'HEAD';
|
||||
/**
|
||||
* DELETE method
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DELETE = 'DELETE';
|
||||
/**
|
||||
* OPTIONS method
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const OPTIONS = 'OPTIONS';
|
||||
/**
|
||||
* TRACE method
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TRACE = 'TRACE';
|
||||
/**
|
||||
* PATCH method
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc5789
|
||||
* @var string
|
||||
*/
|
||||
const PATCH = 'PATCH';
|
||||
/**
|
||||
* Default size of buffer size to read streams
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const BUFFER_SIZE = 1160;
|
||||
/**
|
||||
* Option defaults.
|
||||
*
|
||||
* @see \VendorDuplicator\WpOrg\Requests\Requests::get_default_options()
|
||||
* @see \VendorDuplicator\WpOrg\Requests\Requests::request() for values returned by this method
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
const OPTION_DEFAULTS = ['timeout' => 10, 'connect_timeout' => 10, 'useragent' => 'php-requests/' . self::VERSION, 'protocol_version' => 1.1, 'redirected' => 0, 'redirects' => 10, 'follow_redirects' => \true, 'blocking' => \true, 'type' => self::GET, 'filename' => \false, 'auth' => \false, 'proxy' => \false, 'cookies' => \false, 'max_bytes' => \false, 'idn' => \true, 'hooks' => null, 'transport' => null, 'verify' => null, 'verifyname' => \true];
|
||||
/**
|
||||
* Default supported Transport classes.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
const DEFAULT_TRANSPORTS = [Curl::class => Curl::class, Fsockopen::class => Fsockopen::class];
|
||||
/**
|
||||
* Current version of Requests
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const VERSION = '2.0.9';
|
||||
/**
|
||||
* Selected transport name
|
||||
*
|
||||
* Use {@see \WpOrg\Requests\Requests::get_transport()} instead
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $transport = [];
|
||||
/**
|
||||
* Registered transport classes
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $transports = [];
|
||||
/**
|
||||
* Default certificate path.
|
||||
*
|
||||
* @see \VendorDuplicator\WpOrg\Requests\Requests::get_certificate_path()
|
||||
* @see \VendorDuplicator\WpOrg\Requests\Requests::set_certificate_path()
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $certificate_path = __DIR__ . '/../certificates/cacert.pem';
|
||||
/**
|
||||
* All (known) valid deflate, gzip header magic markers.
|
||||
*
|
||||
* These markers relate to different compression levels.
|
||||
*
|
||||
* @link https://stackoverflow.com/a/43170354/482864 Marker source.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $magic_compression_headers = [
|
||||
"\x1f\x8b" => \true,
|
||||
// Gzip marker.
|
||||
"x\x01" => \true,
|
||||
// Zlib marker - level 1.
|
||||
"x^" => \true,
|
||||
// Zlib marker - level 2 to 5.
|
||||
"x\x9c" => \true,
|
||||
// Zlib marker - level 6.
|
||||
"x\xda" => \true,
|
||||
];
|
||||
/**
|
||||
* This is a static class, do not instantiate it
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
/**
|
||||
* Register a transport
|
||||
*
|
||||
* @param string $transport Transport class to add, must support the \WpOrg\Requests\Transport interface
|
||||
*/
|
||||
public static function add_transport($transport)
|
||||
{
|
||||
if (empty(self::$transports)) {
|
||||
self::$transports = self::DEFAULT_TRANSPORTS;
|
||||
}
|
||||
self::$transports[$transport] = $transport;
|
||||
}
|
||||
/**
|
||||
* Get the fully qualified class name (FQCN) for a working transport.
|
||||
*
|
||||
* @param array<string, bool> $capabilities Optional. Associative array of capabilities to test against, i.e. `['<capability>' => true]`.
|
||||
* @return string FQCN of the transport to use, or an empty string if no transport was
|
||||
* found which provided the requested capabilities.
|
||||
*/
|
||||
protected static function get_transport_class(array $capabilities = [])
|
||||
{
|
||||
// Caching code, don't bother testing coverage.
|
||||
// @codeCoverageIgnoreStart
|
||||
// Array of capabilities as a string to be used as an array key.
|
||||
\ksort($capabilities);
|
||||
$cap_string = \serialize($capabilities);
|
||||
// Don't search for a transport if it's already been done for these $capabilities.
|
||||
if (isset(self::$transport[$cap_string])) {
|
||||
return self::$transport[$cap_string];
|
||||
}
|
||||
// Ensure we will not run this same check again later on.
|
||||
self::$transport[$cap_string] = '';
|
||||
// @codeCoverageIgnoreEnd
|
||||
if (empty(self::$transports)) {
|
||||
self::$transports = self::DEFAULT_TRANSPORTS;
|
||||
}
|
||||
// Find us a working transport.
|
||||
foreach (self::$transports as $class) {
|
||||
if (!\class_exists($class)) {
|
||||
continue;
|
||||
}
|
||||
$result = $class::test($capabilities);
|
||||
if ($result === \true) {
|
||||
self::$transport[$cap_string] = $class;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return self::$transport[$cap_string];
|
||||
}
|
||||
/**
|
||||
* Get a working transport.
|
||||
*
|
||||
* @param array<string, bool> $capabilities Optional. Associative array of capabilities to test against, i.e. `['<capability>' => true]`.
|
||||
* @return \VendorDuplicator\WpOrg\Requests\Transport
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception If no valid transport is found (`notransport`).
|
||||
*/
|
||||
protected static function get_transport(array $capabilities = [])
|
||||
{
|
||||
$class = self::get_transport_class($capabilities);
|
||||
if ($class === '') {
|
||||
throw new Exception('No working transports found', 'notransport', self::$transports);
|
||||
}
|
||||
return new $class();
|
||||
}
|
||||
/**
|
||||
* Checks to see if we have a transport for the capabilities requested.
|
||||
*
|
||||
* Supported capabilities can be found in the {@see \WpOrg\Requests\Capability}
|
||||
* interface as constants.
|
||||
*
|
||||
* Example usage:
|
||||
* `Requests::has_capabilities([Capability::SSL => true])`.
|
||||
*
|
||||
* @param array<string, bool> $capabilities Optional. Associative array of capabilities to test against, i.e. `['<capability>' => true]`.
|
||||
* @return bool Whether the transport has the requested capabilities.
|
||||
*/
|
||||
public static function has_capabilities(array $capabilities = [])
|
||||
{
|
||||
return self::get_transport_class($capabilities) !== '';
|
||||
}
|
||||
/**#@+
|
||||
* @see \VendorDuplicator\WpOrg\Requests\Requests::request()
|
||||
* @param string $url
|
||||
* @param array $headers
|
||||
* @param array $options
|
||||
* @return \VendorDuplicator\WpOrg\Requests\Response
|
||||
*/
|
||||
/**
|
||||
* Send a GET request
|
||||
*/
|
||||
public static function get($url, $headers = [], $options = [])
|
||||
{
|
||||
return self::request($url, $headers, null, self::GET, $options);
|
||||
}
|
||||
/**
|
||||
* Send a HEAD request
|
||||
*/
|
||||
public static function head($url, $headers = [], $options = [])
|
||||
{
|
||||
return self::request($url, $headers, null, self::HEAD, $options);
|
||||
}
|
||||
/**
|
||||
* Send a DELETE request
|
||||
*/
|
||||
public static function delete($url, $headers = [], $options = [])
|
||||
{
|
||||
return self::request($url, $headers, null, self::DELETE, $options);
|
||||
}
|
||||
/**
|
||||
* Send a TRACE request
|
||||
*/
|
||||
public static function trace($url, $headers = [], $options = [])
|
||||
{
|
||||
return self::request($url, $headers, null, self::TRACE, $options);
|
||||
}
|
||||
/**#@-*/
|
||||
/**#@+
|
||||
* @see \VendorDuplicator\WpOrg\Requests\Requests::request()
|
||||
* @param string $url
|
||||
* @param array $headers
|
||||
* @param array $data
|
||||
* @param array $options
|
||||
* @return \VendorDuplicator\WpOrg\Requests\Response
|
||||
*/
|
||||
/**
|
||||
* Send a POST request
|
||||
*/
|
||||
public static function post($url, $headers = [], $data = [], $options = [])
|
||||
{
|
||||
return self::request($url, $headers, $data, self::POST, $options);
|
||||
}
|
||||
/**
|
||||
* Send a PUT request
|
||||
*/
|
||||
public static function put($url, $headers = [], $data = [], $options = [])
|
||||
{
|
||||
return self::request($url, $headers, $data, self::PUT, $options);
|
||||
}
|
||||
/**
|
||||
* Send an OPTIONS request
|
||||
*/
|
||||
public static function options($url, $headers = [], $data = [], $options = [])
|
||||
{
|
||||
return self::request($url, $headers, $data, self::OPTIONS, $options);
|
||||
}
|
||||
/**
|
||||
* Send a PATCH request
|
||||
*
|
||||
* Note: Unlike {@see \WpOrg\Requests\Requests::post()} and {@see \WpOrg\Requests\Requests::put()},
|
||||
* `$headers` is required, as the specification recommends that should send an ETag
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc5789
|
||||
*/
|
||||
public static function patch($url, $headers, $data = [], $options = [])
|
||||
{
|
||||
return self::request($url, $headers, $data, self::PATCH, $options);
|
||||
}
|
||||
/**#@-*/
|
||||
/**
|
||||
* Main interface for HTTP requests
|
||||
*
|
||||
* This method initiates a request and sends it via a transport before
|
||||
* parsing.
|
||||
*
|
||||
* The `$options` parameter takes an associative array with the following
|
||||
* options:
|
||||
*
|
||||
* - `timeout`: How long should we wait for a response?
|
||||
* Note: for cURL, a minimum of 1 second applies, as DNS resolution
|
||||
* operates at second-resolution only.
|
||||
* (float, seconds with a millisecond precision, default: 10, example: 0.01)
|
||||
* - `connect_timeout`: How long should we wait while trying to connect?
|
||||
* (float, seconds with a millisecond precision, default: 10, example: 0.01)
|
||||
* - `useragent`: Useragent to send to the server
|
||||
* (string, default: php-requests/$version)
|
||||
* - `follow_redirects`: Should we follow 3xx redirects?
|
||||
* (boolean, default: true)
|
||||
* - `redirects`: How many times should we redirect before erroring?
|
||||
* (integer, default: 10)
|
||||
* - `blocking`: Should we block processing on this request?
|
||||
* (boolean, default: true)
|
||||
* - `filename`: File to stream the body to instead.
|
||||
* (string|boolean, default: false)
|
||||
* - `auth`: Authentication handler or array of user/password details to use
|
||||
* for Basic authentication
|
||||
* (\WpOrg\Requests\Auth|array|boolean, default: false)
|
||||
* - `proxy`: Proxy details to use for proxy by-passing and authentication
|
||||
* (\WpOrg\Requests\Proxy|array|string|boolean, default: false)
|
||||
* - `max_bytes`: Limit for the response body size.
|
||||
* (integer|boolean, default: false)
|
||||
* - `idn`: Enable IDN parsing
|
||||
* (boolean, default: true)
|
||||
* - `transport`: Custom transport. Either a class name, or a
|
||||
* transport object. Defaults to the first working transport from
|
||||
* {@see \WpOrg\Requests\Requests::getTransport()}
|
||||
* (string|\WpOrg\Requests\Transport, default: {@see \WpOrg\Requests\Requests::getTransport()})
|
||||
* - `hooks`: Hooks handler.
|
||||
* (\WpOrg\Requests\HookManager, default: new WpOrg\Requests\Hooks())
|
||||
* - `verify`: Should we verify SSL certificates? Allows passing in a custom
|
||||
* certificate file as a string. (Using true uses the system-wide root
|
||||
* certificate store instead, but this may have different behaviour
|
||||
* across transports.)
|
||||
* (string|boolean, default: certificates/cacert.pem)
|
||||
* - `verifyname`: Should we verify the common name in the SSL certificate?
|
||||
* (boolean, default: true)
|
||||
* - `data_format`: How should we send the `$data` parameter?
|
||||
* (string, one of 'query' or 'body', default: 'query' for
|
||||
* HEAD/GET/DELETE, 'body' for POST/PUT/OPTIONS/PATCH)
|
||||
*
|
||||
* @param string|Stringable $url URL to request
|
||||
* @param array $headers Extra headers to send with the request
|
||||
* @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
|
||||
* @param string $type HTTP request type (use Requests constants)
|
||||
* @param array $options Options for the request (see description for more information)
|
||||
* @return \VendorDuplicator\WpOrg\Requests\Response
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string or Stringable.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $type argument is not a string.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception On invalid URLs (`nonhttp`)
|
||||
*/
|
||||
public static function request($url, $headers = [], $data = [], $type = self::GET, $options = [])
|
||||
{
|
||||
if (InputValidator::is_string_or_stringable($url) === \false) {
|
||||
throw InvalidArgument::create(1, '$url', 'string|Stringable', \gettype($url));
|
||||
}
|
||||
if (\is_string($type) === \false) {
|
||||
throw InvalidArgument::create(4, '$type', 'string', \gettype($type));
|
||||
}
|
||||
if (\is_array($options) === \false) {
|
||||
throw InvalidArgument::create(5, '$options', 'array', \gettype($options));
|
||||
}
|
||||
if (empty($options['type'])) {
|
||||
$options['type'] = $type;
|
||||
}
|
||||
$options = \array_merge(self::get_default_options(), $options);
|
||||
self::set_defaults($url, $headers, $data, $type, $options);
|
||||
$options['hooks']->dispatch('requests.before_request', [&$url, &$headers, &$data, &$type, &$options]);
|
||||
if (!empty($options['transport'])) {
|
||||
$transport = $options['transport'];
|
||||
if (\is_string($options['transport'])) {
|
||||
$transport = new $transport();
|
||||
}
|
||||
} else {
|
||||
$need_ssl = \stripos($url, 'https://') === 0;
|
||||
$capabilities = [Capability::SSL => $need_ssl];
|
||||
$transport = self::get_transport($capabilities);
|
||||
}
|
||||
$response = $transport->request($url, $headers, $data, $options);
|
||||
$options['hooks']->dispatch('requests.before_parse', [&$response, $url, $headers, $data, $type, $options]);
|
||||
return self::parse_response($response, $url, $headers, $data, $options);
|
||||
}
|
||||
/**
|
||||
* Send multiple HTTP requests simultaneously
|
||||
*
|
||||
* The `$requests` parameter takes an associative or indexed array of
|
||||
* request fields. The key of each request can be used to match up the
|
||||
* request with the returned data, or with the request passed into your
|
||||
* `multiple.request.complete` callback.
|
||||
*
|
||||
* The request fields value is an associative array with the following keys:
|
||||
*
|
||||
* - `url`: Request URL Same as the `$url` parameter to
|
||||
* {@see \WpOrg\Requests\Requests::request()}
|
||||
* (string, required)
|
||||
* - `headers`: Associative array of header fields. Same as the `$headers`
|
||||
* parameter to {@see \WpOrg\Requests\Requests::request()}
|
||||
* (array, default: `array()`)
|
||||
* - `data`: Associative array of data fields or a string. Same as the
|
||||
* `$data` parameter to {@see \WpOrg\Requests\Requests::request()}
|
||||
* (array|string, default: `array()`)
|
||||
* - `type`: HTTP request type (use \WpOrg\Requests\Requests constants). Same as the `$type`
|
||||
* parameter to {@see \WpOrg\Requests\Requests::request()}
|
||||
* (string, default: `\WpOrg\Requests\Requests::GET`)
|
||||
* - `cookies`: Associative array of cookie name to value, or cookie jar.
|
||||
* (array|\WpOrg\Requests\Cookie\Jar)
|
||||
*
|
||||
* If the `$options` parameter is specified, individual requests will
|
||||
* inherit options from it. This can be used to use a single hooking system,
|
||||
* or set all the types to `\WpOrg\Requests\Requests::POST`, for example.
|
||||
*
|
||||
* In addition, the `$options` parameter takes the following global options:
|
||||
*
|
||||
* - `complete`: A callback for when a request is complete. Takes two
|
||||
* parameters, a \WpOrg\Requests\Response/\WpOrg\Requests\Exception reference, and the
|
||||
* ID from the request array (Note: this can also be overridden on a
|
||||
* per-request basis, although that's a little silly)
|
||||
* (callback)
|
||||
*
|
||||
* @param array $requests Requests data (see description for more information)
|
||||
* @param array $options Global and default options (see {@see \WpOrg\Requests\Requests::request()})
|
||||
* @return array Responses (either \WpOrg\Requests\Response or a \WpOrg\Requests\Exception object)
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
|
||||
*/
|
||||
public static function request_multiple($requests, $options = [])
|
||||
{
|
||||
if (InputValidator::has_array_access($requests) === \false || InputValidator::is_iterable($requests) === \false) {
|
||||
throw InvalidArgument::create(1, '$requests', 'array|ArrayAccess&Traversable', \gettype($requests));
|
||||
}
|
||||
if (\is_array($options) === \false) {
|
||||
throw InvalidArgument::create(2, '$options', 'array', \gettype($options));
|
||||
}
|
||||
$options = \array_merge(self::get_default_options(\true), $options);
|
||||
if (!empty($options['hooks'])) {
|
||||
$options['hooks']->register('transport.internal.parse_response', [static::class, 'parse_multiple']);
|
||||
if (!empty($options['complete'])) {
|
||||
$options['hooks']->register('multiple.request.complete', $options['complete']);
|
||||
}
|
||||
}
|
||||
foreach ($requests as $id => &$request) {
|
||||
if (!isset($request['headers'])) {
|
||||
$request['headers'] = [];
|
||||
}
|
||||
if (!isset($request['data'])) {
|
||||
$request['data'] = [];
|
||||
}
|
||||
if (!isset($request['type'])) {
|
||||
$request['type'] = self::GET;
|
||||
}
|
||||
if (!isset($request['options'])) {
|
||||
$request['options'] = $options;
|
||||
$request['options']['type'] = $request['type'];
|
||||
} else {
|
||||
if (empty($request['options']['type'])) {
|
||||
$request['options']['type'] = $request['type'];
|
||||
}
|
||||
$request['options'] = \array_merge($options, $request['options']);
|
||||
}
|
||||
self::set_defaults($request['url'], $request['headers'], $request['data'], $request['type'], $request['options']);
|
||||
// Ensure we only hook in once
|
||||
if ($request['options']['hooks'] !== $options['hooks']) {
|
||||
$request['options']['hooks']->register('transport.internal.parse_response', [static::class, 'parse_multiple']);
|
||||
if (!empty($request['options']['complete'])) {
|
||||
$request['options']['hooks']->register('multiple.request.complete', $request['options']['complete']);
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($request);
|
||||
if (!empty($options['transport'])) {
|
||||
$transport = $options['transport'];
|
||||
if (\is_string($options['transport'])) {
|
||||
$transport = new $transport();
|
||||
}
|
||||
} else {
|
||||
$transport = self::get_transport();
|
||||
}
|
||||
$responses = $transport->request_multiple($requests, $options);
|
||||
foreach ($responses as $id => &$response) {
|
||||
// If our hook got messed with somehow, ensure we end up with the
|
||||
// correct response
|
||||
if (\is_string($response)) {
|
||||
$request = $requests[$id];
|
||||
self::parse_multiple($response, $request);
|
||||
$request['options']['hooks']->dispatch('multiple.request.complete', [&$response, $id]);
|
||||
}
|
||||
}
|
||||
return $responses;
|
||||
}
|
||||
/**
|
||||
* Get the default options
|
||||
*
|
||||
* @see \VendorDuplicator\WpOrg\Requests\Requests::request() for values returned by this method
|
||||
* @param boolean $multirequest Is this a multirequest?
|
||||
* @return array Default option values
|
||||
*/
|
||||
protected static function get_default_options($multirequest = \false)
|
||||
{
|
||||
$defaults = static::OPTION_DEFAULTS;
|
||||
$defaults['verify'] = self::$certificate_path;
|
||||
if ($multirequest !== \false) {
|
||||
$defaults['complete'] = null;
|
||||
}
|
||||
return $defaults;
|
||||
}
|
||||
/**
|
||||
* Get default certificate path.
|
||||
*
|
||||
* @return string Default certificate path.
|
||||
*/
|
||||
public static function get_certificate_path()
|
||||
{
|
||||
return self::$certificate_path;
|
||||
}
|
||||
/**
|
||||
* Set default certificate path.
|
||||
*
|
||||
* @param string|Stringable|bool $path Certificate path, pointing to a PEM file.
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string, Stringable or boolean.
|
||||
*/
|
||||
public static function set_certificate_path($path)
|
||||
{
|
||||
if (InputValidator::is_string_or_stringable($path) === \false && \is_bool($path) === \false) {
|
||||
throw InvalidArgument::create(1, '$path', 'string|Stringable|bool', \gettype($path));
|
||||
}
|
||||
self::$certificate_path = $path;
|
||||
}
|
||||
/**
|
||||
* Set the default values
|
||||
*
|
||||
* The $options parameter is updated with the results.
|
||||
*
|
||||
* @param string $url URL to request
|
||||
* @param array $headers Extra headers to send with the request
|
||||
* @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
|
||||
* @param string $type HTTP request type
|
||||
* @param array $options Options for the request
|
||||
* @return void
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception When the $url is not an http(s) URL.
|
||||
*/
|
||||
protected static function set_defaults(&$url, &$headers, &$data, &$type, &$options)
|
||||
{
|
||||
if (!\preg_match('/^http(s)?:\\/\\//i', $url, $matches)) {
|
||||
throw new Exception('Only HTTP(S) requests are handled.', 'nonhttp', $url);
|
||||
}
|
||||
if (empty($options['hooks'])) {
|
||||
$options['hooks'] = new Hooks();
|
||||
}
|
||||
if (\is_array($options['auth'])) {
|
||||
$options['auth'] = new Basic($options['auth']);
|
||||
}
|
||||
if ($options['auth'] !== \false) {
|
||||
$options['auth']->register($options['hooks']);
|
||||
}
|
||||
if (\is_string($options['proxy']) || \is_array($options['proxy'])) {
|
||||
$options['proxy'] = new Http($options['proxy']);
|
||||
}
|
||||
if ($options['proxy'] !== \false) {
|
||||
$options['proxy']->register($options['hooks']);
|
||||
}
|
||||
if (\is_array($options['cookies'])) {
|
||||
$options['cookies'] = new Jar($options['cookies']);
|
||||
} elseif (empty($options['cookies'])) {
|
||||
$options['cookies'] = new Jar();
|
||||
}
|
||||
if ($options['cookies'] !== \false) {
|
||||
$options['cookies']->register($options['hooks']);
|
||||
}
|
||||
if ($options['idn'] !== \false) {
|
||||
$iri = new Iri($url);
|
||||
$iri->host = IdnaEncoder::encode($iri->ihost);
|
||||
$url = $iri->uri;
|
||||
}
|
||||
// Massage the type to ensure we support it.
|
||||
$type = \strtoupper($type);
|
||||
if (!isset($options['data_format'])) {
|
||||
if (\in_array($type, [self::HEAD, self::GET, self::DELETE], \true)) {
|
||||
$options['data_format'] = 'query';
|
||||
} else {
|
||||
$options['data_format'] = 'body';
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* HTTP response parser
|
||||
*
|
||||
* @param string $headers Full response text including headers and body
|
||||
* @param string $url Original request URL
|
||||
* @param array $req_headers Original $headers array passed to {@link request()}, in case we need to follow redirects
|
||||
* @param array $req_data Original $data array passed to {@link request()}, in case we need to follow redirects
|
||||
* @param array $options Original $options array passed to {@link request()}, in case we need to follow redirects
|
||||
* @return \VendorDuplicator\WpOrg\Requests\Response
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception On missing head/body separator (`requests.no_crlf_separator`)
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception On missing head/body separator (`noversion`)
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception On missing head/body separator (`toomanyredirects`)
|
||||
*/
|
||||
protected static function parse_response($headers, $url, $req_headers, $req_data, $options)
|
||||
{
|
||||
$return = new Response();
|
||||
if (!$options['blocking']) {
|
||||
return $return;
|
||||
}
|
||||
$return->raw = $headers;
|
||||
$return->url = (string) $url;
|
||||
$return->body = '';
|
||||
if (!$options['filename']) {
|
||||
$pos = \strpos($headers, "\r\n\r\n");
|
||||
if ($pos === \false) {
|
||||
// Crap!
|
||||
throw new Exception('Missing header/body separator', 'requests.no_crlf_separator');
|
||||
}
|
||||
$headers = \substr($return->raw, 0, $pos);
|
||||
// Headers will always be separated from the body by two new lines - `\n\r\n\r`.
|
||||
$body = \substr($return->raw, $pos + 4);
|
||||
if (!empty($body)) {
|
||||
$return->body = $body;
|
||||
}
|
||||
}
|
||||
// Pretend CRLF = LF for compatibility (RFC 2616, section 19.3)
|
||||
$headers = \str_replace("\r\n", "\n", $headers);
|
||||
// Unfold headers (replace [CRLF] 1*( SP | HT ) with SP) as per RFC 2616 (section 2.2)
|
||||
$headers = \preg_replace('/\\n[ \\t]/', ' ', $headers);
|
||||
$headers = \explode("\n", $headers);
|
||||
\preg_match('#^HTTP/(1\\.\\d)[ \\t]+(\\d+)#i', \array_shift($headers), $matches);
|
||||
if (empty($matches)) {
|
||||
throw new Exception('Response could not be parsed', 'noversion', $headers);
|
||||
}
|
||||
$return->protocol_version = (float) $matches[1];
|
||||
$return->status_code = (int) $matches[2];
|
||||
if ($return->status_code >= 200 && $return->status_code < 300) {
|
||||
$return->success = \true;
|
||||
}
|
||||
foreach ($headers as $header) {
|
||||
list($key, $value) = \explode(':', $header, 2);
|
||||
$value = \trim($value);
|
||||
\preg_replace('#(\\s+)#i', ' ', $value);
|
||||
$return->headers[$key] = $value;
|
||||
}
|
||||
if (isset($return->headers['transfer-encoding'])) {
|
||||
$return->body = self::decode_chunked($return->body);
|
||||
unset($return->headers['transfer-encoding']);
|
||||
}
|
||||
if (isset($return->headers['content-encoding'])) {
|
||||
$return->body = self::decompress($return->body);
|
||||
}
|
||||
//fsockopen and cURL compatibility
|
||||
if (isset($return->headers['connection'])) {
|
||||
unset($return->headers['connection']);
|
||||
}
|
||||
$options['hooks']->dispatch('requests.before_redirect_check', [&$return, $req_headers, $req_data, $options]);
|
||||
if ($return->is_redirect() && $options['follow_redirects'] === \true) {
|
||||
if (isset($return->headers['location']) && $options['redirected'] < $options['redirects']) {
|
||||
if ($return->status_code === 303) {
|
||||
$options['type'] = self::GET;
|
||||
}
|
||||
$options['redirected']++;
|
||||
$location = $return->headers['location'];
|
||||
if (\strpos($location, 'http://') !== 0 && \strpos($location, 'https://') !== 0) {
|
||||
// relative redirect, for compatibility make it absolute
|
||||
$location = Iri::absolutize($url, $location);
|
||||
$location = $location->uri;
|
||||
}
|
||||
$hook_args = [&$location, &$req_headers, &$req_data, &$options, $return];
|
||||
$options['hooks']->dispatch('requests.before_redirect', $hook_args);
|
||||
$redirected = self::request($location, $req_headers, $req_data, $options['type'], $options);
|
||||
$redirected->history[] = $return;
|
||||
return $redirected;
|
||||
} elseif ($options['redirected'] >= $options['redirects']) {
|
||||
throw new Exception('Too many redirects', 'toomanyredirects', $return);
|
||||
}
|
||||
}
|
||||
$return->redirects = $options['redirected'];
|
||||
$options['hooks']->dispatch('requests.after_request', [&$return, $req_headers, $req_data, $options]);
|
||||
return $return;
|
||||
}
|
||||
/**
|
||||
* Callback for `transport.internal.parse_response`
|
||||
*
|
||||
* Internal use only. Converts a raw HTTP response to a \WpOrg\Requests\Response
|
||||
* while still executing a multiple request.
|
||||
*
|
||||
* `$response` is either set to a \WpOrg\Requests\Response instance, or a \WpOrg\Requests\Exception object
|
||||
*
|
||||
* @param string $response Full response text including headers and body (will be overwritten with Response instance)
|
||||
* @param array $request Request data as passed into {@see \WpOrg\Requests\Requests::request_multiple()}
|
||||
* @return void
|
||||
*/
|
||||
public static function parse_multiple(&$response, $request)
|
||||
{
|
||||
try {
|
||||
$url = $request['url'];
|
||||
$headers = $request['headers'];
|
||||
$data = $request['data'];
|
||||
$options = $request['options'];
|
||||
$response = self::parse_response($response, $url, $headers, $data, $options);
|
||||
} catch (Exception $e) {
|
||||
$response = $e;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Decoded a chunked body as per RFC 2616
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc2616#section-3.6.1
|
||||
* @param string $data Chunked body
|
||||
* @return string Decoded body
|
||||
*/
|
||||
protected static function decode_chunked($data)
|
||||
{
|
||||
if (!\preg_match('/^([0-9a-f]+)(?:;(?:[\\w-]*)(?:=(?:(?:[\\w-]*)*|"(?:[^\\r\\n])*"))?)*\\r\\n/i', \trim($data))) {
|
||||
return $data;
|
||||
}
|
||||
$decoded = '';
|
||||
$encoded = $data;
|
||||
while (\true) {
|
||||
$is_chunked = (bool) \preg_match('/^([0-9a-f]+)(?:;(?:[\\w-]*)(?:=(?:(?:[\\w-]*)*|"(?:[^\\r\\n])*"))?)*\\r\\n/i', $encoded, $matches);
|
||||
if (!$is_chunked) {
|
||||
// Looks like it's not chunked after all
|
||||
return $data;
|
||||
}
|
||||
$length = \hexdec(\trim($matches[1]));
|
||||
if ($length === 0) {
|
||||
// Ignore trailer headers
|
||||
return $decoded;
|
||||
}
|
||||
$chunk_length = \strlen($matches[0]);
|
||||
$decoded .= \substr($encoded, $chunk_length, $length);
|
||||
$encoded = \substr($encoded, $chunk_length + $length + 2);
|
||||
if (\trim($encoded) === '0' || empty($encoded)) {
|
||||
return $decoded;
|
||||
}
|
||||
}
|
||||
// We'll never actually get down here
|
||||
// @codeCoverageIgnoreStart
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
/**
|
||||
* Convert a key => value array to a 'key: value' array for headers
|
||||
*
|
||||
* @param iterable $dictionary Dictionary of header values
|
||||
* @return array List of headers
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed argument is not iterable.
|
||||
*/
|
||||
public static function flatten($dictionary)
|
||||
{
|
||||
if (InputValidator::is_iterable($dictionary) === \false) {
|
||||
throw InvalidArgument::create(1, '$dictionary', 'iterable', \gettype($dictionary));
|
||||
}
|
||||
$return = [];
|
||||
foreach ($dictionary as $key => $value) {
|
||||
$return[] = \sprintf('%s: %s', $key, $value);
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
/**
|
||||
* Decompress an encoded body
|
||||
*
|
||||
* Implements gzip, compress and deflate. Guesses which it is by attempting
|
||||
* to decode.
|
||||
*
|
||||
* @param string $data Compressed data in one of the above formats
|
||||
* @return string Decompressed string
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string.
|
||||
*/
|
||||
public static function decompress($data)
|
||||
{
|
||||
if (\is_string($data) === \false) {
|
||||
throw InvalidArgument::create(1, '$data', 'string', \gettype($data));
|
||||
}
|
||||
if (\trim($data) === '') {
|
||||
// Empty body does not need further processing.
|
||||
return $data;
|
||||
}
|
||||
$marker = \substr($data, 0, 2);
|
||||
if (!isset(self::$magic_compression_headers[$marker])) {
|
||||
// Not actually compressed. Probably cURL ruining this for us.
|
||||
return $data;
|
||||
}
|
||||
if (\function_exists('gzdecode')) {
|
||||
$decoded = @\gzdecode($data);
|
||||
if ($decoded !== \false) {
|
||||
return $decoded;
|
||||
}
|
||||
}
|
||||
if (\function_exists('gzinflate')) {
|
||||
$decoded = @\gzinflate($data);
|
||||
if ($decoded !== \false) {
|
||||
return $decoded;
|
||||
}
|
||||
}
|
||||
$decoded = self::compatible_gzinflate($data);
|
||||
if ($decoded !== \false) {
|
||||
return $decoded;
|
||||
}
|
||||
if (\function_exists('gzuncompress')) {
|
||||
$decoded = @\gzuncompress($data);
|
||||
if ($decoded !== \false) {
|
||||
return $decoded;
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
/**
|
||||
* Decompression of deflated string while staying compatible with the majority of servers.
|
||||
*
|
||||
* Certain Servers will return deflated data with headers which PHP's gzinflate()
|
||||
* function cannot handle out of the box. The following function has been created from
|
||||
* various snippets on the gzinflate() PHP documentation.
|
||||
*
|
||||
* Warning: Magic numbers within. Due to the potential different formats that the compressed
|
||||
* data may be returned in, some "magic offsets" are needed to ensure proper decompression
|
||||
* takes place. For a simple progmatic way to determine the magic offset in use, see:
|
||||
* https://core.trac.wordpress.org/ticket/18273
|
||||
*
|
||||
* @since 1.6.0
|
||||
* @link https://core.trac.wordpress.org/ticket/18273
|
||||
* @link https://www.php.net/gzinflate#70875
|
||||
* @link https://www.php.net/gzinflate#77336
|
||||
*
|
||||
* @param string $gz_data String to decompress.
|
||||
* @return string|bool False on failure.
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string.
|
||||
*/
|
||||
public static function compatible_gzinflate($gz_data)
|
||||
{
|
||||
if (\is_string($gz_data) === \false) {
|
||||
throw InvalidArgument::create(1, '$gz_data', 'string', \gettype($gz_data));
|
||||
}
|
||||
if (\trim($gz_data) === '') {
|
||||
return \false;
|
||||
}
|
||||
// Compressed data might contain a full zlib header, if so strip it for
|
||||
// gzinflate()
|
||||
if (\substr($gz_data, 0, 3) === "\x1f\x8b\x08") {
|
||||
$i = 10;
|
||||
$flg = \ord(\substr($gz_data, 3, 1));
|
||||
if ($flg > 0) {
|
||||
if ($flg & 4) {
|
||||
list($xlen) = \unpack('v', \substr($gz_data, $i, 2));
|
||||
$i += 2 + $xlen;
|
||||
}
|
||||
if ($flg & 8) {
|
||||
$i = \strpos($gz_data, "\x00", $i) + 1;
|
||||
}
|
||||
if ($flg & 16) {
|
||||
$i = \strpos($gz_data, "\x00", $i) + 1;
|
||||
}
|
||||
if ($flg & 2) {
|
||||
$i += 2;
|
||||
}
|
||||
}
|
||||
$decompressed = self::compatible_gzinflate(\substr($gz_data, $i));
|
||||
if ($decompressed !== \false) {
|
||||
return $decompressed;
|
||||
}
|
||||
}
|
||||
// If the data is Huffman Encoded, we must first strip the leading 2
|
||||
// byte Huffman marker for gzinflate()
|
||||
// The response is Huffman coded by many compressors such as
|
||||
// java.util.zip.Deflater, Ruby's Zlib::Deflate, and .NET's
|
||||
// System.IO.Compression.DeflateStream.
|
||||
//
|
||||
// See https://decompres.blogspot.com/ for a quick explanation of this
|
||||
// data type
|
||||
$huffman_encoded = \false;
|
||||
// low nibble of first byte should be 0x08
|
||||
list(, $first_nibble) = \unpack('h', $gz_data);
|
||||
// First 2 bytes should be divisible by 0x1F
|
||||
list(, $first_two_bytes) = \unpack('n', $gz_data);
|
||||
if ($first_nibble === 0x8 && $first_two_bytes % 0x1f === 0) {
|
||||
$huffman_encoded = \true;
|
||||
}
|
||||
if ($huffman_encoded) {
|
||||
$decompressed = @\gzinflate(\substr($gz_data, 2));
|
||||
if ($decompressed !== \false) {
|
||||
return $decompressed;
|
||||
}
|
||||
}
|
||||
if (\substr($gz_data, 0, 4) === "PK\x03\x04") {
|
||||
// ZIP file format header
|
||||
// Offset 6: 2 bytes, General-purpose field
|
||||
// Offset 26: 2 bytes, filename length
|
||||
// Offset 28: 2 bytes, optional field length
|
||||
// Offset 30: Filename field, followed by optional field, followed
|
||||
// immediately by data
|
||||
list(, $general_purpose_flag) = \unpack('v', \substr($gz_data, 6, 2));
|
||||
// If the file has been compressed on the fly, 0x08 bit is set of
|
||||
// the general purpose field. We can use this to differentiate
|
||||
// between a compressed document, and a ZIP file
|
||||
$zip_compressed_on_the_fly = (0x8 & $general_purpose_flag) === 0x8;
|
||||
if (!$zip_compressed_on_the_fly) {
|
||||
// Don't attempt to decode a compressed zip file
|
||||
return $gz_data;
|
||||
}
|
||||
// Determine the first byte of data, based on the above ZIP header
|
||||
// offsets:
|
||||
$first_file_start = \array_sum(\unpack('v2', \substr($gz_data, 26, 4)));
|
||||
$decompressed = @\gzinflate(\substr($gz_data, 30 + $first_file_start));
|
||||
if ($decompressed !== \false) {
|
||||
return $decompressed;
|
||||
}
|
||||
return \false;
|
||||
}
|
||||
// Finally fall back to straight gzinflate
|
||||
$decompressed = @\gzinflate($gz_data);
|
||||
if ($decompressed !== \false) {
|
||||
return $decompressed;
|
||||
}
|
||||
// Fallback for all above failing, not expected, but included for
|
||||
// debugging and preventing regressions and to track stats
|
||||
$decompressed = @\gzinflate(\substr($gz_data, 2));
|
||||
if ($decompressed !== \false) {
|
||||
return $decompressed;
|
||||
}
|
||||
return \false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* HTTP response class
|
||||
*
|
||||
* Contains a response from \WpOrg\Requests\Requests::request()
|
||||
*
|
||||
* @package Requests
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Cookie\Jar;
|
||||
use VendorDuplicator\WpOrg\Requests\Exception;
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Http;
|
||||
use VendorDuplicator\WpOrg\Requests\Response\Headers;
|
||||
/**
|
||||
* HTTP response class
|
||||
*
|
||||
* Contains a response from \WpOrg\Requests\Requests::request()
|
||||
*
|
||||
* @package Requests
|
||||
*/
|
||||
class Response
|
||||
{
|
||||
/**
|
||||
* Response body
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $body = '';
|
||||
/**
|
||||
* Raw HTTP data from the transport
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $raw = '';
|
||||
/**
|
||||
* Headers, as an associative array
|
||||
*
|
||||
* @var \VendorDuplicator\WpOrg\Requests\Response\Headers Array-like object representing headers
|
||||
*/
|
||||
public $headers = [];
|
||||
/**
|
||||
* Status code, false if non-blocking
|
||||
*
|
||||
* @var integer|boolean
|
||||
*/
|
||||
public $status_code = \false;
|
||||
/**
|
||||
* Protocol version, false if non-blocking
|
||||
*
|
||||
* @var float|boolean
|
||||
*/
|
||||
public $protocol_version = \false;
|
||||
/**
|
||||
* Whether the request succeeded or not
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $success = \false;
|
||||
/**
|
||||
* Number of redirects the request used
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $redirects = 0;
|
||||
/**
|
||||
* URL requested
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $url = '';
|
||||
/**
|
||||
* Previous requests (from redirects)
|
||||
*
|
||||
* @var array Array of \WpOrg\Requests\Response objects
|
||||
*/
|
||||
public $history = [];
|
||||
/**
|
||||
* Cookies from the request
|
||||
*
|
||||
* @var \VendorDuplicator\WpOrg\Requests\Cookie\Jar Array-like object representing a cookie jar
|
||||
*/
|
||||
public $cookies = [];
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->headers = new Headers();
|
||||
$this->cookies = new Jar();
|
||||
}
|
||||
/**
|
||||
* Is the response a redirect?
|
||||
*
|
||||
* @return boolean True if redirect (3xx status), false if not.
|
||||
*/
|
||||
public function is_redirect()
|
||||
{
|
||||
$code = $this->status_code;
|
||||
return \in_array($code, [300, 301, 302, 303, 307], \true) || $code > 307 && $code < 400;
|
||||
}
|
||||
/**
|
||||
* Throws an exception if the request was not successful
|
||||
*
|
||||
* @param boolean $allow_redirects Set to false to throw on a 3xx as well
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception If `$allow_redirects` is false, and code is 3xx (`response.no_redirects`)
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\Http On non-successful status code. Exception class corresponds to "Status" + code (e.g. {@see \WpOrg\Requests\Exception\Http\Status404})
|
||||
*/
|
||||
public function throw_for_status($allow_redirects = \true)
|
||||
{
|
||||
if ($this->is_redirect()) {
|
||||
if ($allow_redirects !== \true) {
|
||||
throw new Exception('Redirection not allowed', 'response.no_redirects', $this);
|
||||
}
|
||||
} elseif (!$this->success) {
|
||||
$exception = Http::get_class($this->status_code);
|
||||
throw new $exception(null, $this);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* JSON decode the response body.
|
||||
*
|
||||
* The method parameters are the same as those for the PHP native `json_decode()` function.
|
||||
*
|
||||
* @link https://php.net/json-decode
|
||||
*
|
||||
* @param bool|null $associative Optional. When `true`, JSON objects will be returned as associative arrays;
|
||||
* When `false`, JSON objects will be returned as objects.
|
||||
* When `null`, JSON objects will be returned as associative arrays
|
||||
* or objects depending on whether `JSON_OBJECT_AS_ARRAY` is set in the flags.
|
||||
* Defaults to `true` (in contrast to the PHP native default of `null`).
|
||||
* @param int $depth Optional. Maximum nesting depth of the structure being decoded.
|
||||
* Defaults to `512`.
|
||||
* @param int $options Optional. Bitmask of JSON_BIGINT_AS_STRING, JSON_INVALID_UTF8_IGNORE,
|
||||
* JSON_INVALID_UTF8_SUBSTITUTE, JSON_OBJECT_AS_ARRAY, JSON_THROW_ON_ERROR.
|
||||
* Defaults to `0` (no options set).
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception If `$this->body` is not valid json.
|
||||
*/
|
||||
public function decode_body($associative = \true, $depth = 512, $options = 0)
|
||||
{
|
||||
$data = \json_decode($this->body, $associative, $depth, $options);
|
||||
if (\json_last_error() !== \JSON_ERROR_NONE) {
|
||||
$last_error = \json_last_error_msg();
|
||||
throw new Exception('Unable to parse JSON data: ' . $last_error, 'response.invalid', $this);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Case-insensitive dictionary, suitable for HTTP headers
|
||||
*
|
||||
* @package Requests
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Response;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception;
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument;
|
||||
use VendorDuplicator\WpOrg\Requests\Utility\CaseInsensitiveDictionary;
|
||||
use VendorDuplicator\WpOrg\Requests\Utility\FilteredIterator;
|
||||
/**
|
||||
* Case-insensitive dictionary, suitable for HTTP headers
|
||||
*
|
||||
* @package Requests
|
||||
*/
|
||||
class Headers extends CaseInsensitiveDictionary
|
||||
{
|
||||
/**
|
||||
* Get the given header
|
||||
*
|
||||
* Unlike {@see \WpOrg\Requests\Response\Headers::getValues()}, this returns a string. If there are
|
||||
* multiple values, it concatenates them with a comma as per RFC2616.
|
||||
*
|
||||
* Avoid using this where commas may be used unquoted in values, such as
|
||||
* Set-Cookie headers.
|
||||
*
|
||||
* @param string $offset Name of the header to retrieve.
|
||||
* @return string|null Header value
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
if (\is_string($offset)) {
|
||||
$offset = \strtolower($offset);
|
||||
}
|
||||
if (!isset($this->data[$offset])) {
|
||||
return null;
|
||||
}
|
||||
return $this->flatten($this->data[$offset]);
|
||||
}
|
||||
/**
|
||||
* Set the given item
|
||||
*
|
||||
* @param string $offset Item name
|
||||
* @param string $value Item value
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception On attempting to use dictionary as list (`invalidset`)
|
||||
*/
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
if ($offset === null) {
|
||||
throw new Exception('Object is a dictionary, not a list', 'invalidset');
|
||||
}
|
||||
if (\is_string($offset)) {
|
||||
$offset = \strtolower($offset);
|
||||
}
|
||||
if (!isset($this->data[$offset])) {
|
||||
$this->data[$offset] = [];
|
||||
}
|
||||
$this->data[$offset][] = $value;
|
||||
}
|
||||
/**
|
||||
* Get all values for a given header
|
||||
*
|
||||
* @param string $offset Name of the header to retrieve.
|
||||
* @return array|null Header values
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed argument is not valid as an array key.
|
||||
*/
|
||||
public function getValues($offset)
|
||||
{
|
||||
if (!\is_string($offset) && !\is_int($offset)) {
|
||||
throw InvalidArgument::create(1, '$offset', 'string|int', \gettype($offset));
|
||||
}
|
||||
if (\is_string($offset)) {
|
||||
$offset = \strtolower($offset);
|
||||
}
|
||||
if (!isset($this->data[$offset])) {
|
||||
return null;
|
||||
}
|
||||
return $this->data[$offset];
|
||||
}
|
||||
/**
|
||||
* Flattens a value into a string
|
||||
*
|
||||
* Converts an array into a string by imploding values with a comma, as per
|
||||
* RFC2616's rules for folding headers.
|
||||
*
|
||||
* @param string|array $value Value to flatten
|
||||
* @return string Flattened value
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string or an array.
|
||||
*/
|
||||
public function flatten($value)
|
||||
{
|
||||
if (\is_string($value)) {
|
||||
return $value;
|
||||
}
|
||||
if (\is_array($value)) {
|
||||
return \implode(',', $value);
|
||||
}
|
||||
throw InvalidArgument::create(1, '$value', 'string|array', \gettype($value));
|
||||
}
|
||||
/**
|
||||
* Get an iterator for the data
|
||||
*
|
||||
* Converts the internally stored values to a comma-separated string if there is more
|
||||
* than one value for a key.
|
||||
*
|
||||
* @return \ArrayIterator
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
return new FilteredIterator($this->data, [$this, 'flatten']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Session handler for persistent requests and default parameters
|
||||
*
|
||||
* @package Requests\SessionHandler
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Cookie\Jar;
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument;
|
||||
use VendorDuplicator\WpOrg\Requests\Iri;
|
||||
use VendorDuplicator\WpOrg\Requests\Requests;
|
||||
use VendorDuplicator\WpOrg\Requests\Utility\InputValidator;
|
||||
/**
|
||||
* Session handler for persistent requests and default parameters
|
||||
*
|
||||
* Allows various options to be set as default values, and merges both the
|
||||
* options and URL properties together. A base URL can be set for all requests,
|
||||
* with all subrequests resolved from this. Base options can be set (including
|
||||
* a shared cookie jar), then overridden for individual requests.
|
||||
*
|
||||
* @package Requests\SessionHandler
|
||||
*/
|
||||
class Session
|
||||
{
|
||||
/**
|
||||
* Base URL for requests
|
||||
*
|
||||
* URLs will be made absolute using this as the base
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $url = null;
|
||||
/**
|
||||
* Base headers for requests
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $headers = [];
|
||||
/**
|
||||
* Base data for requests
|
||||
*
|
||||
* If both the base data and the per-request data are arrays, the data will
|
||||
* be merged before sending the request.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $data = [];
|
||||
/**
|
||||
* Base options for requests
|
||||
*
|
||||
* The base options are merged with the per-request data for each request.
|
||||
* The only default option is a shared cookie jar between requests.
|
||||
*
|
||||
* Values here can also be set directly via properties on the Session
|
||||
* object, e.g. `$session->useragent = 'X';`
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $options = [];
|
||||
/**
|
||||
* Create a new session
|
||||
*
|
||||
* @param string|Stringable|null $url Base URL for requests
|
||||
* @param array $headers Default headers for requests
|
||||
* @param array $data Default data for requests
|
||||
* @param array $options Default options for requests
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string, Stringable or null.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $headers argument is not an array.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $data argument is not an array.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
|
||||
*/
|
||||
public function __construct($url = null, $headers = [], $data = [], $options = [])
|
||||
{
|
||||
if ($url !== null && InputValidator::is_string_or_stringable($url) === \false) {
|
||||
throw InvalidArgument::create(1, '$url', 'string|Stringable|null', \gettype($url));
|
||||
}
|
||||
if (\is_array($headers) === \false) {
|
||||
throw InvalidArgument::create(2, '$headers', 'array', \gettype($headers));
|
||||
}
|
||||
if (\is_array($data) === \false) {
|
||||
throw InvalidArgument::create(3, '$data', 'array', \gettype($data));
|
||||
}
|
||||
if (\is_array($options) === \false) {
|
||||
throw InvalidArgument::create(4, '$options', 'array', \gettype($options));
|
||||
}
|
||||
$this->url = $url;
|
||||
$this->headers = $headers;
|
||||
$this->data = $data;
|
||||
$this->options = $options;
|
||||
if (empty($this->options['cookies'])) {
|
||||
$this->options['cookies'] = new Jar();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get a property's value
|
||||
*
|
||||
* @param string $name Property name.
|
||||
* @return mixed|null Property value, null if none found
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
if (isset($this->options[$name])) {
|
||||
return $this->options[$name];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Set a property's value
|
||||
*
|
||||
* @param string $name Property name.
|
||||
* @param mixed $value Property value
|
||||
*/
|
||||
public function __set($name, $value)
|
||||
{
|
||||
$this->options[$name] = $value;
|
||||
}
|
||||
/**
|
||||
* Remove a property's value
|
||||
*
|
||||
* @param string $name Property name.
|
||||
*/
|
||||
public function __isset($name)
|
||||
{
|
||||
return isset($this->options[$name]);
|
||||
}
|
||||
/**
|
||||
* Remove a property's value
|
||||
*
|
||||
* @param string $name Property name.
|
||||
*/
|
||||
public function __unset($name)
|
||||
{
|
||||
unset($this->options[$name]);
|
||||
}
|
||||
/**#@+
|
||||
* @see \VendorDuplicator\WpOrg\Requests\Session::request()
|
||||
* @param string $url
|
||||
* @param array $headers
|
||||
* @param array $options
|
||||
* @return \VendorDuplicator\WpOrg\Requests\Response
|
||||
*/
|
||||
/**
|
||||
* Send a GET request
|
||||
*/
|
||||
public function get($url, $headers = [], $options = [])
|
||||
{
|
||||
return $this->request($url, $headers, null, Requests::GET, $options);
|
||||
}
|
||||
/**
|
||||
* Send a HEAD request
|
||||
*/
|
||||
public function head($url, $headers = [], $options = [])
|
||||
{
|
||||
return $this->request($url, $headers, null, Requests::HEAD, $options);
|
||||
}
|
||||
/**
|
||||
* Send a DELETE request
|
||||
*/
|
||||
public function delete($url, $headers = [], $options = [])
|
||||
{
|
||||
return $this->request($url, $headers, null, Requests::DELETE, $options);
|
||||
}
|
||||
/**#@-*/
|
||||
/**#@+
|
||||
* @see \VendorDuplicator\WpOrg\Requests\Session::request()
|
||||
* @param string $url
|
||||
* @param array $headers
|
||||
* @param array $data
|
||||
* @param array $options
|
||||
* @return \VendorDuplicator\WpOrg\Requests\Response
|
||||
*/
|
||||
/**
|
||||
* Send a POST request
|
||||
*/
|
||||
public function post($url, $headers = [], $data = [], $options = [])
|
||||
{
|
||||
return $this->request($url, $headers, $data, Requests::POST, $options);
|
||||
}
|
||||
/**
|
||||
* Send a PUT request
|
||||
*/
|
||||
public function put($url, $headers = [], $data = [], $options = [])
|
||||
{
|
||||
return $this->request($url, $headers, $data, Requests::PUT, $options);
|
||||
}
|
||||
/**
|
||||
* Send a PATCH request
|
||||
*
|
||||
* Note: Unlike {@see \WpOrg\Requests\Session::post()} and {@see \WpOrg\Requests\Session::put()},
|
||||
* `$headers` is required, as the specification recommends that should send an ETag
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc5789
|
||||
*/
|
||||
public function patch($url, $headers, $data = [], $options = [])
|
||||
{
|
||||
return $this->request($url, $headers, $data, Requests::PATCH, $options);
|
||||
}
|
||||
/**#@-*/
|
||||
/**
|
||||
* Main interface for HTTP requests
|
||||
*
|
||||
* This method initiates a request and sends it via a transport before
|
||||
* parsing.
|
||||
*
|
||||
* @see \VendorDuplicator\WpOrg\Requests\Requests::request()
|
||||
*
|
||||
* @param string $url URL to request
|
||||
* @param array $headers Extra headers to send with the request
|
||||
* @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
|
||||
* @param string $type HTTP request type (use \WpOrg\Requests\Requests constants)
|
||||
* @param array $options Options for the request (see {@see \WpOrg\Requests\Requests::request()})
|
||||
* @return \VendorDuplicator\WpOrg\Requests\Response
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception On invalid URLs (`nonhttp`)
|
||||
*/
|
||||
public function request($url, $headers = [], $data = [], $type = Requests::GET, $options = [])
|
||||
{
|
||||
$request = $this->merge_request(\compact('url', 'headers', 'data', 'options'));
|
||||
return Requests::request($request['url'], $request['headers'], $request['data'], $type, $request['options']);
|
||||
}
|
||||
/**
|
||||
* Send multiple HTTP requests simultaneously
|
||||
*
|
||||
* @see \VendorDuplicator\WpOrg\Requests\Requests::request_multiple()
|
||||
*
|
||||
* @param array $requests Requests data (see {@see \WpOrg\Requests\Requests::request_multiple()})
|
||||
* @param array $options Global and default options (see {@see \WpOrg\Requests\Requests::request()})
|
||||
* @return array Responses (either \WpOrg\Requests\Response or a \WpOrg\Requests\Exception object)
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
|
||||
*/
|
||||
public function request_multiple($requests, $options = [])
|
||||
{
|
||||
if (InputValidator::has_array_access($requests) === \false || InputValidator::is_iterable($requests) === \false) {
|
||||
throw InvalidArgument::create(1, '$requests', 'array|ArrayAccess&Traversable', \gettype($requests));
|
||||
}
|
||||
if (\is_array($options) === \false) {
|
||||
throw InvalidArgument::create(2, '$options', 'array', \gettype($options));
|
||||
}
|
||||
foreach ($requests as $key => $request) {
|
||||
$requests[$key] = $this->merge_request($request, \false);
|
||||
}
|
||||
$options = \array_merge($this->options, $options);
|
||||
// Disallow forcing the type, as that's a per request setting
|
||||
unset($options['type']);
|
||||
return Requests::request_multiple($requests, $options);
|
||||
}
|
||||
/**
|
||||
* Merge a request's data with the default data
|
||||
*
|
||||
* @param array $request Request data (same form as {@see \WpOrg\Requests\Session::request_multiple()})
|
||||
* @param boolean $merge_options Should we merge options as well?
|
||||
* @return array Request data
|
||||
*/
|
||||
protected function merge_request($request, $merge_options = \true)
|
||||
{
|
||||
if ($this->url !== null) {
|
||||
$request['url'] = Iri::absolutize($this->url, $request['url']);
|
||||
$request['url'] = $request['url']->uri;
|
||||
}
|
||||
if (empty($request['headers'])) {
|
||||
$request['headers'] = [];
|
||||
}
|
||||
$request['headers'] = \array_merge($this->headers, $request['headers']);
|
||||
if (empty($request['data'])) {
|
||||
if (\is_array($this->data)) {
|
||||
$request['data'] = $this->data;
|
||||
}
|
||||
} elseif (\is_array($request['data']) && \is_array($this->data)) {
|
||||
$request['data'] = \array_merge($this->data, $request['data']);
|
||||
}
|
||||
if ($merge_options === \true) {
|
||||
$request['options'] = \array_merge($this->options, $request['options']);
|
||||
// Disallow forcing the type, as that's a per request setting
|
||||
unset($request['options']['type']);
|
||||
}
|
||||
return $request;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SSL utilities for Requests
|
||||
*
|
||||
* @package Requests\Utilities
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument;
|
||||
use VendorDuplicator\WpOrg\Requests\Utility\InputValidator;
|
||||
/**
|
||||
* SSL utilities for Requests
|
||||
*
|
||||
* Collection of utilities for working with and verifying SSL certificates.
|
||||
*
|
||||
* @package Requests\Utilities
|
||||
*/
|
||||
final class Ssl
|
||||
{
|
||||
/**
|
||||
* Verify the certificate against common name and subject alternative names
|
||||
*
|
||||
* Unfortunately, PHP doesn't check the certificate against the alternative
|
||||
* names, leading things like 'https://www.github.com/' to be invalid.
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1
|
||||
*
|
||||
* @param string|Stringable $host Host name to verify against
|
||||
* @param array $cert Certificate data from openssl_x509_parse()
|
||||
* @return bool
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $host argument is not a string or a stringable object.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $cert argument is not an array or array accessible.
|
||||
*/
|
||||
public static function verify_certificate($host, $cert)
|
||||
{
|
||||
if (InputValidator::is_string_or_stringable($host) === \false) {
|
||||
throw InvalidArgument::create(1, '$host', 'string|Stringable', \gettype($host));
|
||||
}
|
||||
if (InputValidator::has_array_access($cert) === \false) {
|
||||
throw InvalidArgument::create(2, '$cert', 'array|ArrayAccess', \gettype($cert));
|
||||
}
|
||||
$has_dns_alt = \false;
|
||||
// Check the subjectAltName
|
||||
if (!empty($cert['extensions']['subjectAltName'])) {
|
||||
$altnames = \explode(',', $cert['extensions']['subjectAltName']);
|
||||
foreach ($altnames as $altname) {
|
||||
$altname = \trim($altname);
|
||||
if (\strpos($altname, 'DNS:') !== 0) {
|
||||
continue;
|
||||
}
|
||||
$has_dns_alt = \true;
|
||||
// Strip the 'DNS:' prefix and trim whitespace
|
||||
$altname = \trim(\substr($altname, 4));
|
||||
// Check for a match
|
||||
if (self::match_domain($host, $altname) === \true) {
|
||||
return \true;
|
||||
}
|
||||
}
|
||||
if ($has_dns_alt === \true) {
|
||||
return \false;
|
||||
}
|
||||
}
|
||||
// Fall back to checking the common name if we didn't get any dNSName
|
||||
// alt names, as per RFC2818
|
||||
if (!empty($cert['subject']['CN'])) {
|
||||
// Check for a match
|
||||
return self::match_domain($host, $cert['subject']['CN']) === \true;
|
||||
}
|
||||
return \false;
|
||||
}
|
||||
/**
|
||||
* Verify that a reference name is valid
|
||||
*
|
||||
* Verifies a dNSName for HTTPS usage, (almost) as per Firefox's rules:
|
||||
* - Wildcards can only occur in a name with more than 3 components
|
||||
* - Wildcards can only occur as the last character in the first
|
||||
* component
|
||||
* - Wildcards may be preceded by additional characters
|
||||
*
|
||||
* We modify these rules to be a bit stricter and only allow the wildcard
|
||||
* character to be the full first component; that is, with the exclusion of
|
||||
* the third rule.
|
||||
*
|
||||
* @param string|Stringable $reference Reference dNSName
|
||||
* @return boolean Is the name valid?
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string or a stringable object.
|
||||
*/
|
||||
public static function verify_reference_name($reference)
|
||||
{
|
||||
if (InputValidator::is_string_or_stringable($reference) === \false) {
|
||||
throw InvalidArgument::create(1, '$reference', 'string|Stringable', \gettype($reference));
|
||||
}
|
||||
if ($reference === '') {
|
||||
return \false;
|
||||
}
|
||||
if (\preg_match('`\\s`', $reference) > 0) {
|
||||
// Whitespace detected. This can never be a dNSName.
|
||||
return \false;
|
||||
}
|
||||
$parts = \explode('.', $reference);
|
||||
if ($parts !== \array_filter($parts)) {
|
||||
// DNSName cannot contain two dots next to each other.
|
||||
return \false;
|
||||
}
|
||||
// Check the first part of the name
|
||||
$first = \array_shift($parts);
|
||||
if (\strpos($first, '*') !== \false) {
|
||||
// Check that the wildcard is the full part
|
||||
if ($first !== '*') {
|
||||
return \false;
|
||||
}
|
||||
// Check that we have at least 3 components (including first)
|
||||
if (\count($parts) < 2) {
|
||||
return \false;
|
||||
}
|
||||
}
|
||||
// Check the remaining parts
|
||||
foreach ($parts as $part) {
|
||||
if (\strpos($part, '*') !== \false) {
|
||||
return \false;
|
||||
}
|
||||
}
|
||||
// Nothing found, verified!
|
||||
return \true;
|
||||
}
|
||||
/**
|
||||
* Match a hostname against a dNSName reference
|
||||
*
|
||||
* @param string|Stringable $host Requested host
|
||||
* @param string|Stringable $reference dNSName to match against
|
||||
* @return boolean Does the domain match?
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When either of the passed arguments is not a string or a stringable object.
|
||||
*/
|
||||
public static function match_domain($host, $reference)
|
||||
{
|
||||
if (InputValidator::is_string_or_stringable($host) === \false) {
|
||||
throw InvalidArgument::create(1, '$host', 'string|Stringable', \gettype($host));
|
||||
}
|
||||
// Check if the reference is blocklisted first
|
||||
if (self::verify_reference_name($reference) !== \true) {
|
||||
return \false;
|
||||
}
|
||||
// Check for a direct match
|
||||
if ((string) $host === (string) $reference) {
|
||||
return \true;
|
||||
}
|
||||
// Calculate the valid wildcard match if the host is not an IP address
|
||||
// Also validates that the host has 3 parts or more, as per Firefox's ruleset,
|
||||
// as a wildcard reference is only allowed with 3 parts or more, so the
|
||||
// comparison will never match if host doesn't contain 3 parts or more as well.
|
||||
if (\ip2long($host) === \false) {
|
||||
$parts = \explode('.', $host);
|
||||
$parts[0] = '*';
|
||||
$wildcard = \implode('.', $parts);
|
||||
if ($wildcard === (string) $reference) {
|
||||
return \true;
|
||||
}
|
||||
}
|
||||
return \false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Base HTTP transport
|
||||
*
|
||||
* @package Requests\Transport
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests;
|
||||
|
||||
/**
|
||||
* Base HTTP transport
|
||||
*
|
||||
* @package Requests\Transport
|
||||
*/
|
||||
interface Transport
|
||||
{
|
||||
/**
|
||||
* Perform a request
|
||||
*
|
||||
* @param string $url URL to request
|
||||
* @param array $headers Associative array of request headers
|
||||
* @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
|
||||
* @param array $options Request options, see {@see \WpOrg\Requests\Requests::response()} for documentation
|
||||
* @return string Raw HTTP result
|
||||
*/
|
||||
public function request($url, $headers = [], $data = [], $options = []);
|
||||
/**
|
||||
* Send multiple requests simultaneously
|
||||
*
|
||||
* @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see \WpOrg\Requests\Transport::request()}
|
||||
* @param array $options Global options, see {@see \WpOrg\Requests\Requests::response()} for documentation
|
||||
* @return array Array of \WpOrg\Requests\Response objects (may contain \WpOrg\Requests\Exception or string responses as well)
|
||||
*/
|
||||
public function request_multiple($requests, $options);
|
||||
/**
|
||||
* Self-test whether the transport can be used.
|
||||
*
|
||||
* The available capabilities to test for can be found in {@see \WpOrg\Requests\Capability}.
|
||||
*
|
||||
* @param array<string, bool> $capabilities Optional. Associative array of capabilities to test against, i.e. `['<capability>' => true]`.
|
||||
* @return bool Whether the transport can be used.
|
||||
*/
|
||||
public static function test($capabilities = []);
|
||||
}
|
||||
@@ -0,0 +1,547 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* cURL HTTP transport
|
||||
*
|
||||
* @package Requests\Transport
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Transport;
|
||||
|
||||
use RecursiveArrayIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use VendorDuplicator\WpOrg\Requests\Capability;
|
||||
use VendorDuplicator\WpOrg\Requests\Exception;
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument;
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\Transport\Curl as CurlException;
|
||||
use VendorDuplicator\WpOrg\Requests\Requests;
|
||||
use VendorDuplicator\WpOrg\Requests\Transport;
|
||||
use VendorDuplicator\WpOrg\Requests\Utility\InputValidator;
|
||||
/**
|
||||
* cURL HTTP transport
|
||||
*
|
||||
* @package Requests\Transport
|
||||
*/
|
||||
final class Curl implements Transport
|
||||
{
|
||||
const CURL_7_10_5 = 0x70a05;
|
||||
const CURL_7_16_2 = 0x71002;
|
||||
/**
|
||||
* Raw HTTP data
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $headers = '';
|
||||
/**
|
||||
* Raw body data
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $response_data = '';
|
||||
/**
|
||||
* Information on the current request
|
||||
*
|
||||
* @var array cURL information array, see {@link https://www.php.net/curl_getinfo}
|
||||
*/
|
||||
public $info;
|
||||
/**
|
||||
* cURL version number
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $version;
|
||||
/**
|
||||
* cURL handle
|
||||
*
|
||||
* @var resource|\CurlHandle Resource in PHP < 8.0, Instance of CurlHandle in PHP >= 8.0.
|
||||
*/
|
||||
private $handle;
|
||||
/**
|
||||
* Hook dispatcher instance
|
||||
*
|
||||
* @var \VendorDuplicator\WpOrg\Requests\Hooks
|
||||
*/
|
||||
private $hooks;
|
||||
/**
|
||||
* Have we finished the headers yet?
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
private $done_headers = \false;
|
||||
/**
|
||||
* If streaming to a file, keep the file pointer
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
private $stream_handle;
|
||||
/**
|
||||
* How many bytes are in the response body?
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $response_bytes;
|
||||
/**
|
||||
* What's the maximum number of bytes we should keep?
|
||||
*
|
||||
* @var int|bool Byte count, or false if no limit.
|
||||
*/
|
||||
private $response_byte_limit;
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$curl = \curl_version();
|
||||
$this->version = $curl['version_number'];
|
||||
$this->handle = \curl_init();
|
||||
\curl_setopt($this->handle, \CURLOPT_HEADER, \false);
|
||||
\curl_setopt($this->handle, \CURLOPT_RETURNTRANSFER, 1);
|
||||
if ($this->version >= self::CURL_7_10_5) {
|
||||
\curl_setopt($this->handle, \CURLOPT_ENCODING, '');
|
||||
}
|
||||
if (\defined('CURLOPT_PROTOCOLS')) {
|
||||
// phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_protocolsFound
|
||||
\curl_setopt($this->handle, \CURLOPT_PROTOCOLS, \CURLPROTO_HTTP | \CURLPROTO_HTTPS);
|
||||
}
|
||||
if (\defined('CURLOPT_REDIR_PROTOCOLS')) {
|
||||
// phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_redir_protocolsFound
|
||||
\curl_setopt($this->handle, \CURLOPT_REDIR_PROTOCOLS, \CURLPROTO_HTTP | \CURLPROTO_HTTPS);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if (\is_resource($this->handle)) {
|
||||
\curl_close($this->handle);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Perform a request
|
||||
*
|
||||
* @param string|Stringable $url URL to request
|
||||
* @param array $headers Associative array of request headers
|
||||
* @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
|
||||
* @param array $options Request options, see {@see \WpOrg\Requests\Requests::response()} for documentation
|
||||
* @return string Raw HTTP result
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string or Stringable.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $headers argument is not an array.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $data parameter is not an array or string.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception On a cURL error (`curlerror`)
|
||||
*/
|
||||
public function request($url, $headers = [], $data = [], $options = [])
|
||||
{
|
||||
if (InputValidator::is_string_or_stringable($url) === \false) {
|
||||
throw InvalidArgument::create(1, '$url', 'string|Stringable', \gettype($url));
|
||||
}
|
||||
if (\is_array($headers) === \false) {
|
||||
throw InvalidArgument::create(2, '$headers', 'array', \gettype($headers));
|
||||
}
|
||||
if (!\is_array($data) && !\is_string($data)) {
|
||||
if ($data === null) {
|
||||
$data = '';
|
||||
} else {
|
||||
throw InvalidArgument::create(3, '$data', 'array|string', \gettype($data));
|
||||
}
|
||||
}
|
||||
if (\is_array($options) === \false) {
|
||||
throw InvalidArgument::create(4, '$options', 'array', \gettype($options));
|
||||
}
|
||||
$this->hooks = $options['hooks'];
|
||||
$this->setup_handle($url, $headers, $data, $options);
|
||||
$options['hooks']->dispatch('curl.before_send', [&$this->handle]);
|
||||
if ($options['filename'] !== \false) {
|
||||
// phpcs:ignore WordPress.PHP.NoSilencedErrors -- Silenced the PHP native warning in favour of throwing an exception.
|
||||
$this->stream_handle = @\fopen($options['filename'], 'wb');
|
||||
if ($this->stream_handle === \false) {
|
||||
$error = \error_get_last();
|
||||
throw new Exception($error['message'], 'fopen');
|
||||
}
|
||||
}
|
||||
$this->response_data = '';
|
||||
$this->response_bytes = 0;
|
||||
$this->response_byte_limit = \false;
|
||||
if ($options['max_bytes'] !== \false) {
|
||||
$this->response_byte_limit = $options['max_bytes'];
|
||||
}
|
||||
if (isset($options['verify'])) {
|
||||
if ($options['verify'] === \false) {
|
||||
\curl_setopt($this->handle, \CURLOPT_SSL_VERIFYHOST, 0);
|
||||
\curl_setopt($this->handle, \CURLOPT_SSL_VERIFYPEER, 0);
|
||||
} elseif (\is_string($options['verify'])) {
|
||||
\curl_setopt($this->handle, \CURLOPT_CAINFO, $options['verify']);
|
||||
}
|
||||
}
|
||||
if (isset($options['verifyname']) && $options['verifyname'] === \false) {
|
||||
\curl_setopt($this->handle, \CURLOPT_SSL_VERIFYHOST, 0);
|
||||
}
|
||||
\curl_exec($this->handle);
|
||||
$response = $this->response_data;
|
||||
$options['hooks']->dispatch('curl.after_send', []);
|
||||
if (\curl_errno($this->handle) === \CURLE_WRITE_ERROR || \curl_errno($this->handle) === \CURLE_BAD_CONTENT_ENCODING) {
|
||||
// Reset encoding and try again
|
||||
\curl_setopt($this->handle, \CURLOPT_ENCODING, 'none');
|
||||
$this->response_data = '';
|
||||
$this->response_bytes = 0;
|
||||
\curl_exec($this->handle);
|
||||
$response = $this->response_data;
|
||||
}
|
||||
$this->process_response($response, $options);
|
||||
// Need to remove the $this reference from the curl handle.
|
||||
// Otherwise \WpOrg\Requests\Transport\Curl won't be garbage collected and the curl_close() will never be called.
|
||||
\curl_setopt($this->handle, \CURLOPT_HEADERFUNCTION, null);
|
||||
\curl_setopt($this->handle, \CURLOPT_WRITEFUNCTION, null);
|
||||
return $this->headers;
|
||||
}
|
||||
/**
|
||||
* Send multiple requests simultaneously
|
||||
*
|
||||
* @param array $requests Request data
|
||||
* @param array $options Global options
|
||||
* @return array Array of \WpOrg\Requests\Response objects (may contain \WpOrg\Requests\Exception or string responses as well)
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
|
||||
*/
|
||||
public function request_multiple($requests, $options)
|
||||
{
|
||||
// If you're not requesting, we can't get any responses ¯\_(ツ)_/¯
|
||||
if (empty($requests)) {
|
||||
return [];
|
||||
}
|
||||
if (InputValidator::has_array_access($requests) === \false || InputValidator::is_iterable($requests) === \false) {
|
||||
throw InvalidArgument::create(1, '$requests', 'array|ArrayAccess&Traversable', \gettype($requests));
|
||||
}
|
||||
if (\is_array($options) === \false) {
|
||||
throw InvalidArgument::create(2, '$options', 'array', \gettype($options));
|
||||
}
|
||||
$multihandle = \curl_multi_init();
|
||||
$subrequests = [];
|
||||
$subhandles = [];
|
||||
$class = \get_class($this);
|
||||
foreach ($requests as $id => $request) {
|
||||
$subrequests[$id] = new $class();
|
||||
$subhandles[$id] = $subrequests[$id]->get_subrequest_handle($request['url'], $request['headers'], $request['data'], $request['options']);
|
||||
$request['options']['hooks']->dispatch('curl.before_multi_add', [&$subhandles[$id]]);
|
||||
\curl_multi_add_handle($multihandle, $subhandles[$id]);
|
||||
}
|
||||
$completed = 0;
|
||||
$responses = [];
|
||||
$subrequestcount = \count($subrequests);
|
||||
$request['options']['hooks']->dispatch('curl.before_multi_exec', [&$multihandle]);
|
||||
do {
|
||||
$active = 0;
|
||||
do {
|
||||
$status = \curl_multi_exec($multihandle, $active);
|
||||
} while ($status === \CURLM_CALL_MULTI_PERFORM);
|
||||
$to_process = [];
|
||||
// Read the information as needed
|
||||
while ($done = \curl_multi_info_read($multihandle)) {
|
||||
$key = \array_search($done['handle'], $subhandles, \true);
|
||||
if (!isset($to_process[$key])) {
|
||||
$to_process[$key] = $done;
|
||||
}
|
||||
}
|
||||
// Parse the finished requests before we start getting the new ones
|
||||
foreach ($to_process as $key => $done) {
|
||||
$options = $requests[$key]['options'];
|
||||
if ($done['result'] !== \CURLE_OK) {
|
||||
//get error string for handle.
|
||||
$reason = \curl_error($done['handle']);
|
||||
$exception = new CurlException($reason, CurlException::EASY, $done['handle'], $done['result']);
|
||||
$responses[$key] = $exception;
|
||||
$options['hooks']->dispatch('transport.internal.parse_error', [&$responses[$key], $requests[$key]]);
|
||||
} else {
|
||||
$responses[$key] = $subrequests[$key]->process_response($subrequests[$key]->response_data, $options);
|
||||
$options['hooks']->dispatch('transport.internal.parse_response', [&$responses[$key], $requests[$key]]);
|
||||
}
|
||||
\curl_multi_remove_handle($multihandle, $done['handle']);
|
||||
\curl_close($done['handle']);
|
||||
if (!\is_string($responses[$key])) {
|
||||
$options['hooks']->dispatch('multiple.request.complete', [&$responses[$key], $key]);
|
||||
}
|
||||
$completed++;
|
||||
}
|
||||
} while ($active || $completed < $subrequestcount);
|
||||
$request['options']['hooks']->dispatch('curl.after_multi_exec', [&$multihandle]);
|
||||
\curl_multi_close($multihandle);
|
||||
return $responses;
|
||||
}
|
||||
/**
|
||||
* Get the cURL handle for use in a multi-request
|
||||
*
|
||||
* @param string $url URL to request
|
||||
* @param array $headers Associative array of request headers
|
||||
* @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
|
||||
* @param array $options Request options, see {@see \WpOrg\Requests\Requests::response()} for documentation
|
||||
* @return resource|\CurlHandle Subrequest's cURL handle
|
||||
*/
|
||||
public function &get_subrequest_handle($url, $headers, $data, $options)
|
||||
{
|
||||
$this->setup_handle($url, $headers, $data, $options);
|
||||
if ($options['filename'] !== \false) {
|
||||
$this->stream_handle = \fopen($options['filename'], 'wb');
|
||||
}
|
||||
$this->response_data = '';
|
||||
$this->response_bytes = 0;
|
||||
$this->response_byte_limit = \false;
|
||||
if ($options['max_bytes'] !== \false) {
|
||||
$this->response_byte_limit = $options['max_bytes'];
|
||||
}
|
||||
$this->hooks = $options['hooks'];
|
||||
return $this->handle;
|
||||
}
|
||||
/**
|
||||
* Setup the cURL handle for the given data
|
||||
*
|
||||
* @param string $url URL to request
|
||||
* @param array $headers Associative array of request headers
|
||||
* @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
|
||||
* @param array $options Request options, see {@see \WpOrg\Requests\Requests::response()} for documentation
|
||||
*/
|
||||
private function setup_handle($url, $headers, $data, $options)
|
||||
{
|
||||
$options['hooks']->dispatch('curl.before_request', [&$this->handle]);
|
||||
// Force closing the connection for old versions of cURL (<7.22).
|
||||
if (!isset($headers['Connection'])) {
|
||||
$headers['Connection'] = 'close';
|
||||
}
|
||||
/**
|
||||
* Add "Expect" header.
|
||||
*
|
||||
* By default, cURL adds a "Expect: 100-Continue" to most requests. This header can
|
||||
* add as much as a second to the time it takes for cURL to perform a request. To
|
||||
* prevent this, we need to set an empty "Expect" header. To match the behaviour of
|
||||
* Guzzle, we'll add the empty header to requests that are smaller than 1 MB and use
|
||||
* HTTP/1.1.
|
||||
*
|
||||
* https://curl.se/mail/lib-2017-07/0013.html
|
||||
*/
|
||||
if (!isset($headers['Expect']) && $options['protocol_version'] === 1.1) {
|
||||
$headers['Expect'] = $this->get_expect_header($data);
|
||||
}
|
||||
$headers = Requests::flatten($headers);
|
||||
if (!empty($data)) {
|
||||
$data_format = $options['data_format'];
|
||||
if ($data_format === 'query') {
|
||||
$url = self::format_get($url, $data);
|
||||
$data = '';
|
||||
} elseif (!\is_string($data)) {
|
||||
$data = \http_build_query($data, '', '&');
|
||||
}
|
||||
}
|
||||
switch ($options['type']) {
|
||||
case Requests::POST:
|
||||
\curl_setopt($this->handle, \CURLOPT_POST, \true);
|
||||
\curl_setopt($this->handle, \CURLOPT_POSTFIELDS, $data);
|
||||
break;
|
||||
case Requests::HEAD:
|
||||
\curl_setopt($this->handle, \CURLOPT_CUSTOMREQUEST, $options['type']);
|
||||
\curl_setopt($this->handle, \CURLOPT_NOBODY, \true);
|
||||
break;
|
||||
case Requests::TRACE:
|
||||
\curl_setopt($this->handle, \CURLOPT_CUSTOMREQUEST, $options['type']);
|
||||
break;
|
||||
case Requests::PATCH:
|
||||
case Requests::PUT:
|
||||
case Requests::DELETE:
|
||||
case Requests::OPTIONS:
|
||||
default:
|
||||
\curl_setopt($this->handle, \CURLOPT_CUSTOMREQUEST, $options['type']);
|
||||
if (!empty($data)) {
|
||||
\curl_setopt($this->handle, \CURLOPT_POSTFIELDS, $data);
|
||||
}
|
||||
}
|
||||
// cURL requires a minimum timeout of 1 second when using the system
|
||||
// DNS resolver, as it uses `alarm()`, which is second resolution only.
|
||||
// There's no way to detect which DNS resolver is being used from our
|
||||
// end, so we need to round up regardless of the supplied timeout.
|
||||
//
|
||||
// https://github.com/curl/curl/blob/4f45240bc84a9aa648c8f7243be7b79e9f9323a5/lib/hostip.c#L606-L609
|
||||
$timeout = \max($options['timeout'], 1);
|
||||
if (\is_int($timeout) || $this->version < self::CURL_7_16_2) {
|
||||
\curl_setopt($this->handle, \CURLOPT_TIMEOUT, \ceil($timeout));
|
||||
} else {
|
||||
// phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_timeout_msFound
|
||||
\curl_setopt($this->handle, \CURLOPT_TIMEOUT_MS, \round($timeout * 1000));
|
||||
}
|
||||
if (\is_int($options['connect_timeout']) || $this->version < self::CURL_7_16_2) {
|
||||
\curl_setopt($this->handle, \CURLOPT_CONNECTTIMEOUT, \ceil($options['connect_timeout']));
|
||||
} else {
|
||||
// phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_connecttimeout_msFound
|
||||
\curl_setopt($this->handle, \CURLOPT_CONNECTTIMEOUT_MS, \round($options['connect_timeout'] * 1000));
|
||||
}
|
||||
\curl_setopt($this->handle, \CURLOPT_URL, $url);
|
||||
\curl_setopt($this->handle, \CURLOPT_USERAGENT, $options['useragent']);
|
||||
if (!empty($headers)) {
|
||||
\curl_setopt($this->handle, \CURLOPT_HTTPHEADER, $headers);
|
||||
}
|
||||
if ($options['protocol_version'] === 1.1) {
|
||||
\curl_setopt($this->handle, \CURLOPT_HTTP_VERSION, \CURL_HTTP_VERSION_1_1);
|
||||
} else {
|
||||
\curl_setopt($this->handle, \CURLOPT_HTTP_VERSION, \CURL_HTTP_VERSION_1_0);
|
||||
}
|
||||
if ($options['blocking'] === \true) {
|
||||
\curl_setopt($this->handle, \CURLOPT_HEADERFUNCTION, [$this, 'stream_headers']);
|
||||
\curl_setopt($this->handle, \CURLOPT_WRITEFUNCTION, [$this, 'stream_body']);
|
||||
\curl_setopt($this->handle, \CURLOPT_BUFFERSIZE, Requests::BUFFER_SIZE);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Process a response
|
||||
*
|
||||
* @param string $response Response data from the body
|
||||
* @param array $options Request options
|
||||
* @return string|false HTTP response data including headers. False if non-blocking.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception If the request resulted in a cURL error.
|
||||
*/
|
||||
public function process_response($response, $options)
|
||||
{
|
||||
if ($options['blocking'] === \false) {
|
||||
$fake_headers = '';
|
||||
$options['hooks']->dispatch('curl.after_request', [&$fake_headers]);
|
||||
return \false;
|
||||
}
|
||||
if ($options['filename'] !== \false && $this->stream_handle) {
|
||||
\fclose($this->stream_handle);
|
||||
$this->headers = \trim($this->headers);
|
||||
} else {
|
||||
$this->headers .= $response;
|
||||
}
|
||||
if (\curl_errno($this->handle)) {
|
||||
$error = \sprintf('cURL error %s: %s', \curl_errno($this->handle), \curl_error($this->handle));
|
||||
throw new Exception($error, 'curlerror', $this->handle);
|
||||
}
|
||||
$this->info = \curl_getinfo($this->handle);
|
||||
$options['hooks']->dispatch('curl.after_request', [&$this->headers, &$this->info]);
|
||||
return $this->headers;
|
||||
}
|
||||
/**
|
||||
* Collect the headers as they are received
|
||||
*
|
||||
* @param resource|\CurlHandle $handle cURL handle
|
||||
* @param string $headers Header string
|
||||
* @return integer Length of provided header
|
||||
*/
|
||||
public function stream_headers($handle, $headers)
|
||||
{
|
||||
// Why do we do this? cURL will send both the final response and any
|
||||
// interim responses, such as a 100 Continue. We don't need that.
|
||||
// (We may want to keep this somewhere just in case)
|
||||
if ($this->done_headers) {
|
||||
$this->headers = '';
|
||||
$this->done_headers = \false;
|
||||
}
|
||||
$this->headers .= $headers;
|
||||
if ($headers === "\r\n") {
|
||||
$this->done_headers = \true;
|
||||
}
|
||||
return \strlen($headers);
|
||||
}
|
||||
/**
|
||||
* Collect data as it's received
|
||||
*
|
||||
* @since 1.6.1
|
||||
*
|
||||
* @param resource|\CurlHandle $handle cURL handle
|
||||
* @param string $data Body data
|
||||
* @return integer Length of provided data
|
||||
*/
|
||||
public function stream_body($handle, $data)
|
||||
{
|
||||
$this->hooks->dispatch('request.progress', [$data, $this->response_bytes, $this->response_byte_limit]);
|
||||
$data_length = \strlen($data);
|
||||
// Are we limiting the response size?
|
||||
if ($this->response_byte_limit) {
|
||||
if ($this->response_bytes === $this->response_byte_limit) {
|
||||
// Already at maximum, move on
|
||||
return $data_length;
|
||||
}
|
||||
if ($this->response_bytes + $data_length > $this->response_byte_limit) {
|
||||
// Limit the length
|
||||
$limited_length = $this->response_byte_limit - $this->response_bytes;
|
||||
$data = \substr($data, 0, $limited_length);
|
||||
}
|
||||
}
|
||||
if ($this->stream_handle) {
|
||||
\fwrite($this->stream_handle, $data);
|
||||
} else {
|
||||
$this->response_data .= $data;
|
||||
}
|
||||
$this->response_bytes += \strlen($data);
|
||||
return $data_length;
|
||||
}
|
||||
/**
|
||||
* Format a URL given GET data
|
||||
*
|
||||
* @param string $url Original URL.
|
||||
* @param array|object $data Data to build query using, see {@link https://www.php.net/http_build_query}
|
||||
* @return string URL with data
|
||||
*/
|
||||
private static function format_get($url, $data)
|
||||
{
|
||||
if (!empty($data)) {
|
||||
$query = '';
|
||||
$url_parts = \parse_url($url);
|
||||
if (empty($url_parts['query'])) {
|
||||
$url_parts['query'] = '';
|
||||
} else {
|
||||
$query = $url_parts['query'];
|
||||
}
|
||||
$query .= '&' . \http_build_query($data, '', '&');
|
||||
$query = \trim($query, '&');
|
||||
if (empty($url_parts['query'])) {
|
||||
$url .= '?' . $query;
|
||||
} else {
|
||||
$url = \str_replace($url_parts['query'], $query, $url);
|
||||
}
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
/**
|
||||
* Self-test whether the transport can be used.
|
||||
*
|
||||
* The available capabilities to test for can be found in {@see \WpOrg\Requests\Capability}.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param array<string, bool> $capabilities Optional. Associative array of capabilities to test against, i.e. `['<capability>' => true]`.
|
||||
* @return bool Whether the transport can be used.
|
||||
*/
|
||||
public static function test($capabilities = [])
|
||||
{
|
||||
if (!\function_exists('curl_init') || !\function_exists('curl_exec')) {
|
||||
return \false;
|
||||
}
|
||||
// If needed, check that our installed curl version supports SSL
|
||||
if (isset($capabilities[Capability::SSL]) && $capabilities[Capability::SSL]) {
|
||||
$curl_version = \curl_version();
|
||||
if (!(\CURL_VERSION_SSL & $curl_version['features'])) {
|
||||
return \false;
|
||||
}
|
||||
}
|
||||
return \true;
|
||||
}
|
||||
/**
|
||||
* Get the correct "Expect" header for the given request data.
|
||||
*
|
||||
* @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD.
|
||||
* @return string The "Expect" header.
|
||||
*/
|
||||
private function get_expect_header($data)
|
||||
{
|
||||
if (!\is_array($data)) {
|
||||
return \strlen((string) $data) >= 1048576 ? '100-Continue' : '';
|
||||
}
|
||||
$bytesize = 0;
|
||||
$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($data));
|
||||
foreach ($iterator as $datum) {
|
||||
$bytesize += \strlen((string) $datum);
|
||||
if ($bytesize >= 1048576) {
|
||||
return '100-Continue';
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,430 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* fsockopen HTTP transport
|
||||
*
|
||||
* @package Requests\Transport
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Transport;
|
||||
|
||||
use VendorDuplicator\WpOrg\Requests\Capability;
|
||||
use VendorDuplicator\WpOrg\Requests\Exception;
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument;
|
||||
use VendorDuplicator\WpOrg\Requests\Port;
|
||||
use VendorDuplicator\WpOrg\Requests\Requests;
|
||||
use VendorDuplicator\WpOrg\Requests\Ssl;
|
||||
use VendorDuplicator\WpOrg\Requests\Transport;
|
||||
use VendorDuplicator\WpOrg\Requests\Utility\CaseInsensitiveDictionary;
|
||||
use VendorDuplicator\WpOrg\Requests\Utility\InputValidator;
|
||||
/**
|
||||
* fsockopen HTTP transport
|
||||
*
|
||||
* @package Requests\Transport
|
||||
*/
|
||||
final class Fsockopen implements Transport
|
||||
{
|
||||
/**
|
||||
* Second to microsecond conversion
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const SECOND_IN_MICROSECONDS = 1000000;
|
||||
/**
|
||||
* Raw HTTP data
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $headers = '';
|
||||
/**
|
||||
* Stream metadata
|
||||
*
|
||||
* @var array Associative array of properties, see {@link https://www.php.net/stream_get_meta_data}
|
||||
*/
|
||||
public $info;
|
||||
/**
|
||||
* What's the maximum number of bytes we should keep?
|
||||
*
|
||||
* @var int|bool Byte count, or false if no limit.
|
||||
*/
|
||||
private $max_bytes = \false;
|
||||
/**
|
||||
* Cache for received connection errors.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $connect_error = '';
|
||||
/**
|
||||
* Perform a request
|
||||
*
|
||||
* @param string|Stringable $url URL to request
|
||||
* @param array $headers Associative array of request headers
|
||||
* @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
|
||||
* @param array $options Request options, see {@see \WpOrg\Requests\Requests::response()} for documentation
|
||||
* @return string Raw HTTP result
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string or Stringable.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $headers argument is not an array.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $data parameter is not an array or string.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception On failure to connect to socket (`fsockopenerror`)
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception On socket timeout (`timeout`)
|
||||
*/
|
||||
public function request($url, $headers = [], $data = [], $options = [])
|
||||
{
|
||||
if (InputValidator::is_string_or_stringable($url) === \false) {
|
||||
throw InvalidArgument::create(1, '$url', 'string|Stringable', \gettype($url));
|
||||
}
|
||||
if (\is_array($headers) === \false) {
|
||||
throw InvalidArgument::create(2, '$headers', 'array', \gettype($headers));
|
||||
}
|
||||
if (!\is_array($data) && !\is_string($data)) {
|
||||
if ($data === null) {
|
||||
$data = '';
|
||||
} else {
|
||||
throw InvalidArgument::create(3, '$data', 'array|string', \gettype($data));
|
||||
}
|
||||
}
|
||||
if (\is_array($options) === \false) {
|
||||
throw InvalidArgument::create(4, '$options', 'array', \gettype($options));
|
||||
}
|
||||
$options['hooks']->dispatch('fsockopen.before_request');
|
||||
$url_parts = \parse_url($url);
|
||||
if (empty($url_parts)) {
|
||||
throw new Exception('Invalid URL.', 'invalidurl', $url);
|
||||
}
|
||||
$host = $url_parts['host'];
|
||||
$context = \stream_context_create();
|
||||
$verifyname = \false;
|
||||
$case_insensitive_headers = new CaseInsensitiveDictionary($headers);
|
||||
// HTTPS support
|
||||
if (isset($url_parts['scheme']) && \strtolower($url_parts['scheme']) === 'https') {
|
||||
$remote_socket = 'ssl://' . $host;
|
||||
if (!isset($url_parts['port'])) {
|
||||
$url_parts['port'] = Port::HTTPS;
|
||||
}
|
||||
$context_options = ['verify_peer' => \true, 'capture_peer_cert' => \true];
|
||||
$verifyname = \true;
|
||||
// SNI, if enabled (OpenSSL >=0.9.8j)
|
||||
// phpcs:ignore PHPCompatibility.Constants.NewConstants.openssl_tlsext_server_nameFound
|
||||
if (\defined('OPENSSL_TLSEXT_SERVER_NAME') && \OPENSSL_TLSEXT_SERVER_NAME) {
|
||||
$context_options['SNI_enabled'] = \true;
|
||||
if (isset($options['verifyname']) && $options['verifyname'] === \false) {
|
||||
$context_options['SNI_enabled'] = \false;
|
||||
}
|
||||
}
|
||||
if (isset($options['verify'])) {
|
||||
if ($options['verify'] === \false) {
|
||||
$context_options['verify_peer'] = \false;
|
||||
$context_options['verify_peer_name'] = \false;
|
||||
$verifyname = \false;
|
||||
} elseif (\is_string($options['verify'])) {
|
||||
$context_options['cafile'] = $options['verify'];
|
||||
}
|
||||
}
|
||||
if (isset($options['verifyname']) && $options['verifyname'] === \false) {
|
||||
$context_options['verify_peer_name'] = \false;
|
||||
$verifyname = \false;
|
||||
}
|
||||
\stream_context_set_option($context, ['ssl' => $context_options]);
|
||||
} else {
|
||||
$remote_socket = 'tcp://' . $host;
|
||||
}
|
||||
$this->max_bytes = $options['max_bytes'];
|
||||
if (!isset($url_parts['port'])) {
|
||||
$url_parts['port'] = Port::HTTP;
|
||||
}
|
||||
$remote_socket .= ':' . $url_parts['port'];
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler
|
||||
\set_error_handler([$this, 'connect_error_handler'], \E_WARNING | \E_NOTICE);
|
||||
$options['hooks']->dispatch('fsockopen.remote_socket', [&$remote_socket]);
|
||||
$socket = \stream_socket_client($remote_socket, $errno, $errstr, \ceil($options['connect_timeout']), \STREAM_CLIENT_CONNECT, $context);
|
||||
\restore_error_handler();
|
||||
if ($verifyname && !$this->verify_certificate_from_context($host, $context)) {
|
||||
throw new Exception('SSL certificate did not match the requested domain name', 'ssl.no_match');
|
||||
}
|
||||
if (!$socket) {
|
||||
if ($errno === 0) {
|
||||
// Connection issue
|
||||
throw new Exception(\rtrim($this->connect_error), 'fsockopen.connect_error');
|
||||
}
|
||||
throw new Exception($errstr, 'fsockopenerror', null, $errno);
|
||||
}
|
||||
$data_format = $options['data_format'];
|
||||
if ($data_format === 'query') {
|
||||
$path = self::format_get($url_parts, $data);
|
||||
$data = '';
|
||||
} else {
|
||||
$path = self::format_get($url_parts, []);
|
||||
}
|
||||
$options['hooks']->dispatch('fsockopen.remote_host_path', [&$path, $url]);
|
||||
$request_body = '';
|
||||
$out = \sprintf("%s %s HTTP/%.1F\r\n", $options['type'], $path, $options['protocol_version']);
|
||||
if ($options['type'] !== Requests::TRACE) {
|
||||
if (\is_array($data)) {
|
||||
$request_body = \http_build_query($data, '', '&');
|
||||
} else {
|
||||
$request_body = $data;
|
||||
}
|
||||
// Always include Content-length on POST requests to prevent
|
||||
// 411 errors from some servers when the body is empty.
|
||||
if (!empty($data) || $options['type'] === Requests::POST) {
|
||||
if (!isset($case_insensitive_headers['Content-Length'])) {
|
||||
$headers['Content-Length'] = \strlen($request_body);
|
||||
}
|
||||
if (!isset($case_insensitive_headers['Content-Type'])) {
|
||||
$headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isset($case_insensitive_headers['Host'])) {
|
||||
$out .= \sprintf('Host: %s', $url_parts['host']);
|
||||
$scheme_lower = \strtolower($url_parts['scheme']);
|
||||
if ($scheme_lower === 'http' && $url_parts['port'] !== Port::HTTP || $scheme_lower === 'https' && $url_parts['port'] !== Port::HTTPS) {
|
||||
$out .= ':' . $url_parts['port'];
|
||||
}
|
||||
$out .= "\r\n";
|
||||
}
|
||||
if (!isset($case_insensitive_headers['User-Agent'])) {
|
||||
$out .= \sprintf("User-Agent: %s\r\n", $options['useragent']);
|
||||
}
|
||||
$accept_encoding = $this->accept_encoding();
|
||||
if (!isset($case_insensitive_headers['Accept-Encoding']) && !empty($accept_encoding)) {
|
||||
$out .= \sprintf("Accept-Encoding: %s\r\n", $accept_encoding);
|
||||
}
|
||||
$headers = Requests::flatten($headers);
|
||||
if (!empty($headers)) {
|
||||
$out .= \implode("\r\n", $headers) . "\r\n";
|
||||
}
|
||||
$options['hooks']->dispatch('fsockopen.after_headers', [&$out]);
|
||||
if (\substr($out, -2) !== "\r\n") {
|
||||
$out .= "\r\n";
|
||||
}
|
||||
if (!isset($case_insensitive_headers['Connection'])) {
|
||||
$out .= "Connection: Close\r\n";
|
||||
}
|
||||
$out .= "\r\n" . $request_body;
|
||||
$options['hooks']->dispatch('fsockopen.before_send', [&$out]);
|
||||
\fwrite($socket, $out);
|
||||
$options['hooks']->dispatch('fsockopen.after_send', [$out]);
|
||||
if (!$options['blocking']) {
|
||||
\fclose($socket);
|
||||
$fake_headers = '';
|
||||
$options['hooks']->dispatch('fsockopen.after_request', [&$fake_headers]);
|
||||
return '';
|
||||
}
|
||||
$timeout_sec = (int) \floor($options['timeout']);
|
||||
if ($timeout_sec === $options['timeout']) {
|
||||
$timeout_msec = 0;
|
||||
} else {
|
||||
$timeout_msec = self::SECOND_IN_MICROSECONDS * $options['timeout'] % self::SECOND_IN_MICROSECONDS;
|
||||
}
|
||||
\stream_set_timeout($socket, $timeout_sec, $timeout_msec);
|
||||
$response = '';
|
||||
$body = '';
|
||||
$headers = '';
|
||||
$this->info = \stream_get_meta_data($socket);
|
||||
$size = 0;
|
||||
$doingbody = \false;
|
||||
$download = \false;
|
||||
if ($options['filename']) {
|
||||
// phpcs:ignore WordPress.PHP.NoSilencedErrors -- Silenced the PHP native warning in favour of throwing an exception.
|
||||
$download = @\fopen($options['filename'], 'wb');
|
||||
if ($download === \false) {
|
||||
$error = \error_get_last();
|
||||
throw new Exception($error['message'], 'fopen');
|
||||
}
|
||||
}
|
||||
while (!\feof($socket)) {
|
||||
$this->info = \stream_get_meta_data($socket);
|
||||
if ($this->info['timed_out']) {
|
||||
throw new Exception('fsocket timed out', 'timeout');
|
||||
}
|
||||
$block = \fread($socket, Requests::BUFFER_SIZE);
|
||||
if (!$doingbody) {
|
||||
$response .= $block;
|
||||
if (\strpos($response, "\r\n\r\n")) {
|
||||
list($headers, $block) = \explode("\r\n\r\n", $response, 2);
|
||||
$doingbody = \true;
|
||||
}
|
||||
}
|
||||
// Are we in body mode now?
|
||||
if ($doingbody) {
|
||||
$options['hooks']->dispatch('request.progress', [$block, $size, $this->max_bytes]);
|
||||
$data_length = \strlen($block);
|
||||
if ($this->max_bytes) {
|
||||
// Have we already hit a limit?
|
||||
if ($size === $this->max_bytes) {
|
||||
continue;
|
||||
}
|
||||
if ($size + $data_length > $this->max_bytes) {
|
||||
// Limit the length
|
||||
$limited_length = $this->max_bytes - $size;
|
||||
$block = \substr($block, 0, $limited_length);
|
||||
}
|
||||
}
|
||||
$size += \strlen($block);
|
||||
if ($download) {
|
||||
\fwrite($download, $block);
|
||||
} else {
|
||||
$body .= $block;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->headers = $headers;
|
||||
if ($download) {
|
||||
\fclose($download);
|
||||
} else {
|
||||
$this->headers .= "\r\n\r\n" . $body;
|
||||
}
|
||||
\fclose($socket);
|
||||
$options['hooks']->dispatch('fsockopen.after_request', [&$this->headers, &$this->info]);
|
||||
return $this->headers;
|
||||
}
|
||||
/**
|
||||
* Send multiple requests simultaneously
|
||||
*
|
||||
* @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see \WpOrg\Requests\Transport::request()}
|
||||
* @param array $options Global options, see {@see \WpOrg\Requests\Requests::response()} for documentation
|
||||
* @return array Array of \WpOrg\Requests\Response objects (may contain \WpOrg\Requests\Exception or string responses as well)
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access.
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
|
||||
*/
|
||||
public function request_multiple($requests, $options)
|
||||
{
|
||||
// If you're not requesting, we can't get any responses ¯\_(ツ)_/¯
|
||||
if (empty($requests)) {
|
||||
return [];
|
||||
}
|
||||
if (InputValidator::has_array_access($requests) === \false || InputValidator::is_iterable($requests) === \false) {
|
||||
throw InvalidArgument::create(1, '$requests', 'array|ArrayAccess&Traversable', \gettype($requests));
|
||||
}
|
||||
if (\is_array($options) === \false) {
|
||||
throw InvalidArgument::create(2, '$options', 'array', \gettype($options));
|
||||
}
|
||||
$responses = [];
|
||||
$class = \get_class($this);
|
||||
foreach ($requests as $id => $request) {
|
||||
try {
|
||||
$handler = new $class();
|
||||
$responses[$id] = $handler->request($request['url'], $request['headers'], $request['data'], $request['options']);
|
||||
$request['options']['hooks']->dispatch('transport.internal.parse_response', [&$responses[$id], $request]);
|
||||
} catch (Exception $e) {
|
||||
$responses[$id] = $e;
|
||||
}
|
||||
if (!\is_string($responses[$id])) {
|
||||
$request['options']['hooks']->dispatch('multiple.request.complete', [&$responses[$id], $id]);
|
||||
}
|
||||
}
|
||||
return $responses;
|
||||
}
|
||||
/**
|
||||
* Retrieve the encodings we can accept
|
||||
*
|
||||
* @return string Accept-Encoding header value
|
||||
*/
|
||||
private static function accept_encoding()
|
||||
{
|
||||
$type = [];
|
||||
if (\function_exists('gzinflate')) {
|
||||
$type[] = 'deflate;q=1.0';
|
||||
}
|
||||
if (\function_exists('gzuncompress')) {
|
||||
$type[] = 'compress;q=0.5';
|
||||
}
|
||||
$type[] = 'gzip;q=0.5';
|
||||
return \implode(', ', $type);
|
||||
}
|
||||
/**
|
||||
* Format a URL given GET data
|
||||
*
|
||||
* @param array $url_parts Array of URL parts as received from {@link https://www.php.net/parse_url}
|
||||
* @param array|object $data Data to build query using, see {@link https://www.php.net/http_build_query}
|
||||
* @return string URL with data
|
||||
*/
|
||||
private static function format_get($url_parts, $data)
|
||||
{
|
||||
if (!empty($data)) {
|
||||
if (empty($url_parts['query'])) {
|
||||
$url_parts['query'] = '';
|
||||
}
|
||||
$url_parts['query'] .= '&' . \http_build_query($data, '', '&');
|
||||
$url_parts['query'] = \trim($url_parts['query'], '&');
|
||||
}
|
||||
if (isset($url_parts['path'])) {
|
||||
if (isset($url_parts['query'])) {
|
||||
$get = $url_parts['path'] . '?' . $url_parts['query'];
|
||||
} else {
|
||||
$get = $url_parts['path'];
|
||||
}
|
||||
} else {
|
||||
$get = '/';
|
||||
}
|
||||
return $get;
|
||||
}
|
||||
/**
|
||||
* Error handler for stream_socket_client()
|
||||
*
|
||||
* @param int $errno Error number (e.g. E_WARNING)
|
||||
* @param string $errstr Error message
|
||||
*/
|
||||
public function connect_error_handler($errno, $errstr)
|
||||
{
|
||||
// Double-check we can handle it
|
||||
if (($errno & \E_WARNING) === 0 && ($errno & \E_NOTICE) === 0) {
|
||||
// Return false to indicate the default error handler should engage
|
||||
return \false;
|
||||
}
|
||||
$this->connect_error .= $errstr . "\n";
|
||||
return \true;
|
||||
}
|
||||
/**
|
||||
* Verify the certificate against common name and subject alternative names
|
||||
*
|
||||
* Unfortunately, PHP doesn't check the certificate against the alternative
|
||||
* names, leading things like 'https://www.github.com/' to be invalid.
|
||||
* Instead
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1
|
||||
*
|
||||
* @param string $host Host name to verify against
|
||||
* @param resource $context Stream context
|
||||
* @return bool
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception On failure to connect via TLS (`fsockopen.ssl.connect_error`)
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`)
|
||||
*/
|
||||
public function verify_certificate_from_context($host, $context)
|
||||
{
|
||||
$meta = \stream_context_get_options($context);
|
||||
// If we don't have SSL options, then we couldn't make the connection at
|
||||
// all
|
||||
if (empty($meta) || empty($meta['ssl']) || empty($meta['ssl']['peer_certificate'])) {
|
||||
throw new Exception(\rtrim($this->connect_error), 'ssl.connect_error');
|
||||
}
|
||||
$cert = \openssl_x509_parse($meta['ssl']['peer_certificate']);
|
||||
return Ssl::verify_certificate($host, $cert);
|
||||
}
|
||||
/**
|
||||
* Self-test whether the transport can be used.
|
||||
*
|
||||
* The available capabilities to test for can be found in {@see \WpOrg\Requests\Capability}.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param array<string, bool> $capabilities Optional. Associative array of capabilities to test against, i.e. `['<capability>' => true]`.
|
||||
* @return bool Whether the transport can be used.
|
||||
*/
|
||||
public static function test($capabilities = [])
|
||||
{
|
||||
if (!\function_exists('fsockopen')) {
|
||||
return \false;
|
||||
}
|
||||
// If needed, check that streams support SSL
|
||||
if (isset($capabilities[Capability::SSL]) && $capabilities[Capability::SSL]) {
|
||||
if (!\extension_loaded('openssl') || !\function_exists('openssl_x509_parse')) {
|
||||
return \false;
|
||||
}
|
||||
}
|
||||
return \true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Case-insensitive dictionary, suitable for HTTP headers
|
||||
*
|
||||
* @package Requests\Utilities
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Utility;
|
||||
|
||||
use ArrayAccess;
|
||||
use ArrayIterator;
|
||||
use IteratorAggregate;
|
||||
use ReturnTypeWillChange;
|
||||
use VendorDuplicator\WpOrg\Requests\Exception;
|
||||
/**
|
||||
* Case-insensitive dictionary, suitable for HTTP headers
|
||||
*
|
||||
* @package Requests\Utilities
|
||||
*/
|
||||
class CaseInsensitiveDictionary implements ArrayAccess, IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* Actual item data
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $data = [];
|
||||
/**
|
||||
* Creates a case insensitive dictionary.
|
||||
*
|
||||
* @param array $data Dictionary/map to convert to case-insensitive
|
||||
*/
|
||||
public function __construct(array $data = [])
|
||||
{
|
||||
foreach ($data as $offset => $value) {
|
||||
$this->offsetSet($offset, $value);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Check if the given item exists
|
||||
*
|
||||
* @param string $offset Item key
|
||||
* @return boolean Does the item exist?
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
if (\is_string($offset)) {
|
||||
$offset = \strtolower($offset);
|
||||
}
|
||||
return isset($this->data[$offset]);
|
||||
}
|
||||
/**
|
||||
* Get the value for the item
|
||||
*
|
||||
* @param string $offset Item key
|
||||
* @return string|null Item value (null if the item key doesn't exist)
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
if (\is_string($offset)) {
|
||||
$offset = \strtolower($offset);
|
||||
}
|
||||
if (!isset($this->data[$offset])) {
|
||||
return null;
|
||||
}
|
||||
return $this->data[$offset];
|
||||
}
|
||||
/**
|
||||
* Set the given item
|
||||
*
|
||||
* @param string $offset Item name
|
||||
* @param string $value Item value
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception On attempting to use dictionary as list (`invalidset`)
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
if ($offset === null) {
|
||||
throw new Exception('Object is a dictionary, not a list', 'invalidset');
|
||||
}
|
||||
if (\is_string($offset)) {
|
||||
$offset = \strtolower($offset);
|
||||
}
|
||||
$this->data[$offset] = $value;
|
||||
}
|
||||
/**
|
||||
* Unset the given header
|
||||
*
|
||||
* @param string $offset The key for the item to unset.
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
if (\is_string($offset)) {
|
||||
$offset = \strtolower($offset);
|
||||
}
|
||||
unset($this->data[$offset]);
|
||||
}
|
||||
/**
|
||||
* Get an iterator for the data
|
||||
*
|
||||
* @return \ArrayIterator
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function getIterator()
|
||||
{
|
||||
return new ArrayIterator($this->data);
|
||||
}
|
||||
/**
|
||||
* Get the headers as an array
|
||||
*
|
||||
* @return array Header data
|
||||
*/
|
||||
public function getAll()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Iterator for arrays requiring filtered values
|
||||
*
|
||||
* @package Requests\Utilities
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Utility;
|
||||
|
||||
use ArrayIterator;
|
||||
use ReturnTypeWillChange;
|
||||
use VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument;
|
||||
use VendorDuplicator\WpOrg\Requests\Utility\InputValidator;
|
||||
/**
|
||||
* Iterator for arrays requiring filtered values
|
||||
*
|
||||
* @package Requests\Utilities
|
||||
*/
|
||||
final class FilteredIterator extends ArrayIterator
|
||||
{
|
||||
/**
|
||||
* Callback to run as a filter
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
private $callback;
|
||||
/**
|
||||
* Create a new iterator
|
||||
*
|
||||
* @param array $data The array or object to be iterated on.
|
||||
* @param callable $callback Callback to be called on each value
|
||||
*
|
||||
* @throws \VendorDuplicator\WpOrg\Requests\Exception\InvalidArgument When the passed $data argument is not iterable.
|
||||
*/
|
||||
public function __construct($data, $callback)
|
||||
{
|
||||
if (InputValidator::is_iterable($data) === \false) {
|
||||
throw InvalidArgument::create(1, '$data', 'iterable', \gettype($data));
|
||||
}
|
||||
parent::__construct($data);
|
||||
if (\is_callable($callback)) {
|
||||
$this->callback = $callback;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Prevent unserialization of the object for security reasons.
|
||||
*
|
||||
* @phpcs:disable PHPCompatibility.FunctionNameRestrictions.NewMagicMethods.__unserializeFound
|
||||
*
|
||||
* @param array $data Restored array of data originally serialized.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function __unserialize($data)
|
||||
{
|
||||
}
|
||||
// phpcs:enable
|
||||
/**
|
||||
* Perform reinitialization tasks.
|
||||
*
|
||||
* Prevents a callback from being injected during unserialization of an object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __wakeup()
|
||||
{
|
||||
unset($this->callback);
|
||||
}
|
||||
/**
|
||||
* Get the current item's value after filtering
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function current()
|
||||
{
|
||||
$value = parent::current();
|
||||
if (\is_callable($this->callback)) {
|
||||
$value = \call_user_func($this->callback, $value);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
/**
|
||||
* Prevent creating a PHP value from a stored representation of the object for security reasons.
|
||||
*
|
||||
* @param string $data The serialized string.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function unserialize($data)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Input validation utilities.
|
||||
*
|
||||
* @package Requests\Utilities
|
||||
*/
|
||||
namespace VendorDuplicator\WpOrg\Requests\Utility;
|
||||
|
||||
use ArrayAccess;
|
||||
use CurlHandle;
|
||||
use Traversable;
|
||||
/**
|
||||
* Input validation utilities.
|
||||
*
|
||||
* @package Requests\Utilities
|
||||
*/
|
||||
final class InputValidator
|
||||
{
|
||||
/**
|
||||
* Verify that a received input parameter is of type string or is "stringable".
|
||||
*
|
||||
* @param mixed $input Input parameter to verify.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_string_or_stringable($input)
|
||||
{
|
||||
return \is_string($input) || self::is_stringable_object($input);
|
||||
}
|
||||
/**
|
||||
* Verify whether a received input parameter is usable as an integer array key.
|
||||
*
|
||||
* @param mixed $input Input parameter to verify.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_numeric_array_key($input)
|
||||
{
|
||||
if (\is_int($input)) {
|
||||
return \true;
|
||||
}
|
||||
if (!\is_string($input)) {
|
||||
return \false;
|
||||
}
|
||||
return (bool) \preg_match('`^-?[0-9]+$`', $input);
|
||||
}
|
||||
/**
|
||||
* Verify whether a received input parameter is "stringable".
|
||||
*
|
||||
* @param mixed $input Input parameter to verify.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_stringable_object($input)
|
||||
{
|
||||
return \is_object($input) && \method_exists($input, '__toString');
|
||||
}
|
||||
/**
|
||||
* Verify whether a received input parameter is _accessible as if it were an array_.
|
||||
*
|
||||
* @param mixed $input Input parameter to verify.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_array_access($input)
|
||||
{
|
||||
return \is_array($input) || $input instanceof ArrayAccess;
|
||||
}
|
||||
/**
|
||||
* Verify whether a received input parameter is "iterable".
|
||||
*
|
||||
* @internal The PHP native `is_iterable()` function was only introduced in PHP 7.1
|
||||
* and this library still supports PHP 5.6.
|
||||
*
|
||||
* @param mixed $input Input parameter to verify.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_iterable($input)
|
||||
{
|
||||
return \is_array($input) || $input instanceof Traversable;
|
||||
}
|
||||
/**
|
||||
* Verify whether a received input parameter is a Curl handle.
|
||||
*
|
||||
* The PHP Curl extension worked with resources prior to PHP 8.0 and with
|
||||
* an instance of the `CurlHandle` class since PHP 8.0.
|
||||
* {@link https://www.php.net/manual/en/migration80.incompatible.php#migration80.incompatible.resource2object}
|
||||
*
|
||||
* @param mixed $input Input parameter to verify.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_curl_handle($input)
|
||||
{
|
||||
if (\is_resource($input)) {
|
||||
return \get_resource_type($input) === 'curl';
|
||||
}
|
||||
if (\is_object($input)) {
|
||||
return $input instanceof CurlHandle;
|
||||
}
|
||||
return \false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user