HTTP API: Revert changeset [52244].

Reverting Requests 2.0.0 changes and moving to WordPress 6.0 cycle. Why? The namespace and file case renaming revealed 2 issues in Core's upgrader process.

See https://core.trac.wordpress.org/ticket/54504#comment:22 for more information.

Follow-up to [52327].

See #54562, #54504.
Built from https://develop.svn.wordpress.org/trunk@52328


git-svn-id: http://core.svn.wordpress.org/trunk@51920 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Sergey Biryukov 2021-12-06 21:30:03 +00:00
parent 8045f6a42b
commit 47c4ff7102
72 changed files with 1806 additions and 3103 deletions

View File

@ -2,13 +2,10 @@
/** /**
* Authentication provider interface * Authentication provider interface
* *
* @package Requests\Authentication * @package Requests
* @subpackage Authentication
*/ */
namespace WpOrg\Requests;
use WpOrg\Requests\Hooks;
/** /**
* Authentication provider interface * Authentication provider interface
* *
@ -17,20 +14,20 @@ use WpOrg\Requests\Hooks;
* Parameters should be passed via the constructor where possible, as this * Parameters should be passed via the constructor where possible, as this
* makes it much easier for users to use your provider. * makes it much easier for users to use your provider.
* *
* @see \WpOrg\Requests\Hooks * @see Requests_Hooks
* * @package Requests
* @package Requests\Authentication * @subpackage Authentication
*/ */
interface Auth { interface Requests_Auth {
/** /**
* Register hooks as needed * Register hooks as needed
* *
* This method is called in {@see \WpOrg\Requests\Requests::request()} when the user * This method is called in {@see Requests::request} when the user has set
* has set an instance as the 'auth' option. Use this callback to register all the * an instance as the 'auth' option. Use this callback to register all the
* hooks you'll need. * hooks you'll need.
* *
* @see \WpOrg\Requests\Hooks::register() * @see Requests_Hooks::register
* @param \WpOrg\Requests\Hooks $hooks Hook system * @param Requests_Hooks $hooks Hook system
*/ */
public function register(Hooks $hooks); public function register(Requests_Hooks $hooks);
} }

View File

@ -2,25 +2,20 @@
/** /**
* Basic Authentication provider * Basic Authentication provider
* *
* @package Requests\Authentication * @package Requests
* @subpackage Authentication
*/ */
namespace WpOrg\Requests\Auth;
use WpOrg\Requests\Auth;
use WpOrg\Requests\Exception\ArgumentCount;
use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\Hooks;
/** /**
* Basic Authentication provider * Basic Authentication provider
* *
* Provides a handler for Basic HTTP authentication via the Authorization * Provides a handler for Basic HTTP authentication via the Authorization
* header. * header.
* *
* @package Requests\Authentication * @package Requests
* @subpackage Authentication
*/ */
class Basic implements Auth { class Requests_Auth_Basic implements Requests_Auth {
/** /**
* Username * Username
* *
@ -38,45 +33,35 @@ class Basic implements Auth {
/** /**
* Constructor * Constructor
* *
* @since 2.0 Throws an `InvalidArgument` exception. * @throws Requests_Exception On incorrect number of arguments (`authbasicbadargs`)
* @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 * @param array|null $args Array of user and password. Must have exactly two elements
*
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not an array or null.
* @throws \WpOrg\Requests\Exception\ArgumentCount On incorrect number of array elements (`authbasicbadargs`).
*/ */
public function __construct($args = null) { public function __construct($args = null) {
if (is_array($args)) { if (is_array($args)) {
if (count($args) !== 2) { if (count($args) !== 2) {
throw ArgumentCount::create('an array with exactly two elements', count($args), 'authbasicbadargs'); throw new Requests_Exception('Invalid number of arguments', 'authbasicbadargs');
} }
list($this->user, $this->pass) = $args; list($this->user, $this->pass) = $args;
return;
}
if ($args !== null) {
throw InvalidArgument::create(1, '$args', 'array|null', gettype($args));
} }
} }
/** /**
* Register the necessary callbacks * Register the necessary callbacks
* *
* @see \WpOrg\Requests\Auth\Basic::curl_before_send() * @see curl_before_send
* @see \WpOrg\Requests\Auth\Basic::fsockopen_header() * @see fsockopen_header
* @param \WpOrg\Requests\Hooks $hooks Hook system * @param Requests_Hooks $hooks Hook system
*/ */
public function register(Hooks $hooks) { public function register(Requests_Hooks $hooks) {
$hooks->register('curl.before_send', [$this, 'curl_before_send']); $hooks->register('curl.before_send', array($this, 'curl_before_send'));
$hooks->register('fsockopen.after_headers', [$this, 'fsockopen_header']); $hooks->register('fsockopen.after_headers', array($this, 'fsockopen_header'));
} }
/** /**
* Set cURL parameters before the data is sent * Set cURL parameters before the data is sent
* *
* @param resource|\CurlHandle $handle cURL handle * @param resource $handle cURL resource
*/ */
public function curl_before_send(&$handle) { public function curl_before_send(&$handle) {
curl_setopt($handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); curl_setopt($handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);

View File

@ -1,187 +0,0 @@
<?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 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('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' => '\WpOrg\Requests\Auth',
'requests_hooker' => '\WpOrg\Requests\HookManager',
'requests_proxy' => '\WpOrg\Requests\Proxy',
'requests_transport' => '\WpOrg\Requests\Transport',
// Classes.
'requests_cookie' => '\WpOrg\Requests\Cookie',
'requests_exception' => '\WpOrg\Requests\Exception',
'requests_hooks' => '\WpOrg\Requests\Hooks',
'requests_idnaencoder' => '\WpOrg\Requests\IdnaEncoder',
'requests_ipv6' => '\WpOrg\Requests\Ipv6',
'requests_iri' => '\WpOrg\Requests\Iri',
'requests_response' => '\WpOrg\Requests\Response',
'requests_session' => '\WpOrg\Requests\Session',
'requests_ssl' => '\WpOrg\Requests\Ssl',
'requests_auth_basic' => '\WpOrg\Requests\Auth\Basic',
'requests_cookie_jar' => '\WpOrg\Requests\Cookie\Jar',
'requests_proxy_http' => '\WpOrg\Requests\Proxy\Http',
'requests_response_headers' => '\WpOrg\Requests\Response\Headers',
'requests_transport_curl' => '\WpOrg\Requests\Transport\Curl',
'requests_transport_fsockopen' => '\WpOrg\Requests\Transport\Fsockopen',
'requests_utility_caseinsensitivedictionary' => '\WpOrg\Requests\Utility\CaseInsensitiveDictionary',
'requests_utility_filterediterator' => '\WpOrg\Requests\Utility\FilteredIterator',
'requests_exception_http' => '\WpOrg\Requests\Exception\Http',
'requests_exception_transport' => '\WpOrg\Requests\Exception\Transport',
'requests_exception_transport_curl' => '\WpOrg\Requests\Exception\Transport\Curl',
'requests_exception_http_304' => '\WpOrg\Requests\Exception\Http\Status304',
'requests_exception_http_305' => '\WpOrg\Requests\Exception\Http\Status305',
'requests_exception_http_306' => '\WpOrg\Requests\Exception\Http\Status306',
'requests_exception_http_400' => '\WpOrg\Requests\Exception\Http\Status400',
'requests_exception_http_401' => '\WpOrg\Requests\Exception\Http\Status401',
'requests_exception_http_402' => '\WpOrg\Requests\Exception\Http\Status402',
'requests_exception_http_403' => '\WpOrg\Requests\Exception\Http\Status403',
'requests_exception_http_404' => '\WpOrg\Requests\Exception\Http\Status404',
'requests_exception_http_405' => '\WpOrg\Requests\Exception\Http\Status405',
'requests_exception_http_406' => '\WpOrg\Requests\Exception\Http\Status406',
'requests_exception_http_407' => '\WpOrg\Requests\Exception\Http\Status407',
'requests_exception_http_408' => '\WpOrg\Requests\Exception\Http\Status408',
'requests_exception_http_409' => '\WpOrg\Requests\Exception\Http\Status409',
'requests_exception_http_410' => '\WpOrg\Requests\Exception\Http\Status410',
'requests_exception_http_411' => '\WpOrg\Requests\Exception\Http\Status411',
'requests_exception_http_412' => '\WpOrg\Requests\Exception\Http\Status412',
'requests_exception_http_413' => '\WpOrg\Requests\Exception\Http\Status413',
'requests_exception_http_414' => '\WpOrg\Requests\Exception\Http\Status414',
'requests_exception_http_415' => '\WpOrg\Requests\Exception\Http\Status415',
'requests_exception_http_416' => '\WpOrg\Requests\Exception\Http\Status416',
'requests_exception_http_417' => '\WpOrg\Requests\Exception\Http\Status417',
'requests_exception_http_418' => '\WpOrg\Requests\Exception\Http\Status418',
'requests_exception_http_428' => '\WpOrg\Requests\Exception\Http\Status428',
'requests_exception_http_429' => '\WpOrg\Requests\Exception\Http\Status429',
'requests_exception_http_431' => '\WpOrg\Requests\Exception\Http\Status431',
'requests_exception_http_500' => '\WpOrg\Requests\Exception\Http\Status500',
'requests_exception_http_501' => '\WpOrg\Requests\Exception\Http\Status501',
'requests_exception_http_502' => '\WpOrg\Requests\Exception\Http\Status502',
'requests_exception_http_503' => '\WpOrg\Requests\Exception\Http\Status503',
'requests_exception_http_504' => '\WpOrg\Requests\Exception\Http\Status504',
'requests_exception_http_505' => '\WpOrg\Requests\Exception\Http\Status505',
'requests_exception_http_511' => '\WpOrg\Requests\Exception\Http\Status511',
'requests_exception_http_unknown' => '\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 "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 Request 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;
}
}
}

View File

@ -1,36 +0,0 @@
<?php
/**
* Capability interface declaring the known capabilities.
*
* @package Requests\Utilities
*/
namespace 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 array<string>
*/
const ALL = [
self::SSL,
];
}

View File

@ -2,23 +2,17 @@
/** /**
* Cookie storage object * Cookie storage object
* *
* @package Requests\Cookies * @package Requests
* @subpackage Cookies
*/ */
namespace WpOrg\Requests;
use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\Iri;
use WpOrg\Requests\Response\Headers;
use WpOrg\Requests\Utility\CaseInsensitiveDictionary;
use WpOrg\Requests\Utility\InputValidator;
/** /**
* Cookie storage object * Cookie storage object
* *
* @package Requests\Cookies * @package Requests
* @subpackage Cookies
*/ */
class Cookie { class Requests_Cookie {
/** /**
* Cookie name. * Cookie name.
* *
@ -39,9 +33,9 @@ class Cookie {
* Valid keys are (currently) path, domain, expires, max-age, secure and * Valid keys are (currently) path, domain, expires, max-age, secure and
* httponly. * httponly.
* *
* @var \WpOrg\Requests\Utility\CaseInsensitiveDictionary|array Array-like object * @var Requests_Utility_CaseInsensitiveDictionary|array Array-like object
*/ */
public $attributes = []; public $attributes = array();
/** /**
* Cookie flags * Cookie flags
@ -51,7 +45,7 @@ class Cookie {
* *
* @var array * @var array
*/ */
public $flags = []; public $flags = array();
/** /**
* Reference time for relative calculations * Reference time for relative calculations
@ -68,46 +62,18 @@ class Cookie {
* *
* @param string $name * @param string $name
* @param string $value * @param string $value
* @param array|\WpOrg\Requests\Utility\CaseInsensitiveDictionary $attributes Associative array of attribute data * @param array|Requests_Utility_CaseInsensitiveDictionary $attributes Associative array of attribute data
* @param array $flags
* @param int|null $reference_time
*
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $name argument is not a string.
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $value argument is not a string.
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $attributes argument is not an array or iterable object with array access.
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $flags argument is not an array.
* @throws \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) { public function __construct($name, $value, $attributes = array(), $flags = array(), $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->name = $name;
$this->value = $value; $this->value = $value;
$this->attributes = $attributes; $this->attributes = $attributes;
$default_flags = [ $default_flags = array(
'creation' => time(), 'creation' => time(),
'last-access' => time(), 'last-access' => time(),
'persistent' => false, 'persistent' => false,
'host-only' => true, 'host-only' => true,
]; );
$this->flags = array_merge($default_flags, $flags); $this->flags = array_merge($default_flags, $flags);
$this->reference_time = time(); $this->reference_time = time();
@ -118,15 +84,6 @@ class Cookie {
$this->normalize(); $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. * Check if a cookie is expired.
* *
@ -156,10 +113,10 @@ class Cookie {
/** /**
* Check if a cookie is valid for a given URI * Check if a cookie is valid for a given URI
* *
* @param \WpOrg\Requests\Iri $uri URI to check * @param Requests_IRI $uri URI to check
* @return boolean Whether the cookie is valid for the given URI * @return boolean Whether the cookie is valid for the given URI
*/ */
public function uri_matches(Iri $uri) { public function uri_matches(Requests_IRI $uri) {
if (!$this->domain_matches($uri->host)) { if (!$this->domain_matches($uri->host)) {
return false; return false;
} }
@ -174,23 +131,19 @@ class Cookie {
/** /**
* Check if a cookie is valid for a given domain * Check if a cookie is valid for a given domain
* *
* @param string $domain Domain to check * @param string $string Domain to check
* @return boolean Whether the cookie is valid for the given domain * @return boolean Whether the cookie is valid for the given domain
*/ */
public function domain_matches($domain) { public function domain_matches($string) {
if (is_string($domain) === false) {
return false;
}
if (!isset($this->attributes['domain'])) { if (!isset($this->attributes['domain'])) {
// Cookies created manually; cookies created by Requests will set // Cookies created manually; cookies created by Requests will set
// the domain to the requested domain // the domain to the requested domain
return true; return true;
} }
$cookie_domain = $this->attributes['domain']; $domain_string = $this->attributes['domain'];
if ($cookie_domain === $domain) { if ($domain_string === $string) {
// The cookie domain and the passed domain are identical. // The domain string and the string are identical.
return true; return true;
} }
@ -200,26 +153,26 @@ class Cookie {
return false; return false;
} }
if (strlen($domain) <= strlen($cookie_domain)) { if (strlen($string) <= strlen($domain_string)) {
// For obvious reasons, the cookie domain cannot be a suffix if the passed domain // For obvious reasons, the string cannot be a suffix if the domain
// is shorter than the cookie domain // is shorter than the domain string
return false; return false;
} }
if (substr($domain, -1 * strlen($cookie_domain)) !== $cookie_domain) { if (substr($string, -1 * strlen($domain_string)) !== $domain_string) {
// The cookie domain should be a suffix of the passed domain. // The domain string should be a suffix of the string.
return false; return false;
} }
$prefix = substr($domain, 0, strlen($domain) - strlen($cookie_domain)); $prefix = substr($string, 0, strlen($string) - strlen($domain_string));
if (substr($prefix, -1) !== '.') { if (substr($prefix, -1) !== '.') {
// The last character of the passed domain that is not included in the // The last character of the string that is not included in the
// domain string should be a %x2E (".") character. // domain string should be a %x2E (".") character.
return false; return false;
} }
// The passed domain should be a host name (i.e., not an IP address). // The string 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); return !preg_match('#^(.+\.)\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$#', $string);
} }
/** /**
@ -242,10 +195,6 @@ class Cookie {
return true; return true;
} }
if (is_scalar($request_path) === false) {
return false;
}
$cookie_path = $this->attributes['path']; $cookie_path = $this->attributes['path'];
if ($cookie_path === $request_path) { if ($cookie_path === $request_path) {
@ -367,6 +316,17 @@ class Cookie {
return sprintf('%s=%s', $this->name, $this->value); return sprintf('%s=%s', $this->name, $this->value);
} }
/**
* Format a cookie for a Cookie header
*
* @codeCoverageIgnore
* @deprecated Use {@see Requests_Cookie::format_for_header}
* @return string
*/
public function formatForHeader() {
return $this->format_for_header();
}
/** /**
* Format a cookie for a Set-Cookie header * Format a cookie for a Set-Cookie header
* *
@ -378,7 +338,7 @@ class Cookie {
public function format_for_set_cookie() { public function format_for_set_cookie() {
$header_value = $this->format_for_header(); $header_value = $this->format_for_header();
if (!empty($this->attributes)) { if (!empty($this->attributes)) {
$parts = []; $parts = array();
foreach ($this->attributes as $key => $value) { foreach ($this->attributes as $key => $value) {
// Ignore non-associative attributes // Ignore non-associative attributes
if (is_numeric($key)) { if (is_numeric($key)) {
@ -394,6 +354,26 @@ class Cookie {
return $header_value; return $header_value;
} }
/**
* Format a cookie for a Set-Cookie header
*
* @codeCoverageIgnore
* @deprecated Use {@see Requests_Cookie::format_for_set_cookie}
* @return string
*/
public function formatForSetCookie() {
return $this->format_for_set_cookie();
}
/**
* Get the cookie value
*
* Attributes and other data can be accessed via methods.
*/
public function __toString() {
return $this->value;
}
/** /**
* Parse a cookie string into a cookie object * Parse a cookie string into a cookie object
* *
@ -401,28 +381,15 @@ class Cookie {
* is an intentional deviation from RFC 2109 and RFC 2616. RFC 6265 * is an intentional deviation from RFC 2109 and RFC 2616. RFC 6265
* specifies some of this handling, but not in a thorough manner. * 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 Cookie header value (from a Set-Cookie header)
* @param string $name * @return Requests_Cookie Parsed cookie object
* @param int|null $reference_time
* @return \WpOrg\Requests\Cookie Parsed cookie object
*
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $cookie_header argument is not a string.
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $name argument is not a string.
*/ */
public static function parse($cookie_header, $name = '', $reference_time = null) { public static function parse($string, $name = '', $reference_time = null) {
if (is_string($cookie_header) === false) { $parts = explode(';', $string);
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); $kvparts = array_shift($parts);
if (!empty($name)) { if (!empty($name)) {
$value = $cookie_header; $value = $string;
} }
elseif (strpos($kvparts, '=') === false) { elseif (strpos($kvparts, '=') === false) {
// Some sites might only have a value without the equals separator. // Some sites might only have a value without the equals separator.
@ -439,8 +406,8 @@ class Cookie {
$name = trim($name); $name = trim($name);
$value = trim($value); $value = trim($value);
// Attribute keys are handled case-insensitively // Attribute key are handled case-insensitively
$attributes = new CaseInsensitiveDictionary(); $attributes = new Requests_Utility_CaseInsensitiveDictionary();
if (!empty($parts)) { if (!empty($parts)) {
foreach ($parts as $part) { foreach ($parts as $part) {
@ -458,24 +425,24 @@ class Cookie {
} }
} }
return new static($name, $value, $attributes, [], $reference_time); return new Requests_Cookie($name, $value, $attributes, array(), $reference_time);
} }
/** /**
* Parse all Set-Cookie headers from request headers * Parse all Set-Cookie headers from request headers
* *
* @param \WpOrg\Requests\Response\Headers $headers Headers to parse from * @param Requests_Response_Headers $headers Headers to parse from
* @param \WpOrg\Requests\Iri|null $origin URI for comparing cookie origins * @param Requests_IRI|null $origin URI for comparing cookie origins
* @param int|null $time Reference time for expiration calculation * @param int|null $time Reference time for expiration calculation
* @return array * @return array
*/ */
public static function parse_from_headers(Headers $headers, Iri $origin = null, $time = null) { public static function parse_from_headers(Requests_Response_Headers $headers, Requests_IRI $origin = null, $time = null) {
$cookie_headers = $headers->getValues('Set-Cookie'); $cookie_headers = $headers->getValues('Set-Cookie');
if (empty($cookie_headers)) { if (empty($cookie_headers)) {
return []; return array();
} }
$cookies = []; $cookies = array();
foreach ($cookie_headers as $header) { foreach ($cookie_headers as $header) {
$parsed = self::parse($header, '', $time); $parsed = self::parse($header, '', $time);
@ -524,4 +491,15 @@ class Cookie {
return $cookies; return $cookies;
} }
/**
* Parse all Set-Cookie headers from request headers
*
* @codeCoverageIgnore
* @deprecated Use {@see Requests_Cookie::parse_from_headers}
* @return array
*/
public static function parseFromHeaders(Requests_Response_Headers $headers) {
return self::parse_from_headers($headers);
}
} }

View File

@ -2,123 +2,112 @@
/** /**
* Cookie holder object * Cookie holder object
* *
* @package Requests\Cookies * @package Requests
* @subpackage Cookies
*/ */
namespace WpOrg\Requests\Cookie;
use ArrayAccess;
use ArrayIterator;
use IteratorAggregate;
use ReturnTypeWillChange;
use WpOrg\Requests\Cookie;
use WpOrg\Requests\Exception;
use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\HookManager;
use WpOrg\Requests\Iri;
use WpOrg\Requests\Response;
/** /**
* Cookie holder object * Cookie holder object
* *
* @package Requests\Cookies * @package Requests
* @subpackage Cookies
*/ */
class Jar implements ArrayAccess, IteratorAggregate { class Requests_Cookie_Jar implements ArrayAccess, IteratorAggregate {
/** /**
* Actual item data * Actual item data
* *
* @var array * @var array
*/ */
protected $cookies = []; protected $cookies = array();
/** /**
* Create a new jar * Create a new jar
* *
* @param array $cookies Existing cookie values * @param array $cookies Existing cookie values
*
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not an array.
*/ */
public function __construct($cookies = []) { public function __construct($cookies = array()) {
if (is_array($cookies) === false) {
throw InvalidArgument::create(1, '$cookies', 'array', gettype($cookies));
}
$this->cookies = $cookies; $this->cookies = $cookies;
} }
/** /**
* Normalise cookie data into a \WpOrg\Requests\Cookie * Normalise cookie data into a Requests_Cookie
* *
* @param string|\WpOrg\Requests\Cookie $cookie * @param string|Requests_Cookie $cookie
* @return \WpOrg\Requests\Cookie * @return Requests_Cookie
*/ */
public function normalize_cookie($cookie, $key = '') { public function normalize_cookie($cookie, $key = null) {
if ($cookie instanceof Cookie) { if ($cookie instanceof Requests_Cookie) {
return $cookie; return $cookie;
} }
return Cookie::parse($cookie, $key); return Requests_Cookie::parse($cookie, $key);
}
/**
* Normalise cookie data into a Requests_Cookie
*
* @codeCoverageIgnore
* @deprecated Use {@see Requests_Cookie_Jar::normalize_cookie}
* @return Requests_Cookie
*/
public function normalizeCookie($cookie, $key = null) {
return $this->normalize_cookie($cookie, $key);
} }
/** /**
* Check if the given item exists * Check if the given item exists
* *
* @param string $offset Item key * @param string $key Item key
* @return boolean Does the item exist? * @return boolean Does the item exist?
*/ */
#[ReturnTypeWillChange] public function offsetExists($key) {
public function offsetExists($offset) { return isset($this->cookies[$key]);
return isset($this->cookies[$offset]);
} }
/** /**
* Get the value for the item * Get the value for the item
* *
* @param string $offset Item key * @param string $key Item key
* @return string|null Item value (null if offsetExists is false) * @return string|null Item value (null if offsetExists is false)
*/ */
#[ReturnTypeWillChange] public function offsetGet($key) {
public function offsetGet($offset) { if (!isset($this->cookies[$key])) {
if (!isset($this->cookies[$offset])) {
return null; return null;
} }
return $this->cookies[$offset]; return $this->cookies[$key];
} }
/** /**
* Set the given item * Set the given item
* *
* @param string $offset Item name * @throws Requests_Exception On attempting to use dictionary as list (`invalidset`)
* @param string $value Item value
* *
* @throws \WpOrg\Requests\Exception On attempting to use dictionary as list (`invalidset`) * @param string $key Item name
* @param string $value Item value
*/ */
#[ReturnTypeWillChange] public function offsetSet($key, $value) {
public function offsetSet($offset, $value) { if ($key === null) {
if ($offset === null) { throw new Requests_Exception('Object is a dictionary, not a list', 'invalidset');
throw new Exception('Object is a dictionary, not a list', 'invalidset');
} }
$this->cookies[$offset] = $value; $this->cookies[$key] = $value;
} }
/** /**
* Unset the given header * Unset the given header
* *
* @param string $offset * @param string $key
*/ */
#[ReturnTypeWillChange] public function offsetUnset($key) {
public function offsetUnset($offset) { unset($this->cookies[$key]);
unset($this->cookies[$offset]);
} }
/** /**
* Get an iterator for the data * Get an iterator for the data
* *
* @return \ArrayIterator * @return ArrayIterator
*/ */
#[ReturnTypeWillChange]
public function getIterator() { public function getIterator() {
return new ArrayIterator($this->cookies); return new ArrayIterator($this->cookies);
} }
@ -126,11 +115,11 @@ class Jar implements ArrayAccess, IteratorAggregate {
/** /**
* Register the cookie handler with the request's hooking system * Register the cookie handler with the request's hooking system
* *
* @param \WpOrg\Requests\HookManager $hooks Hooking system * @param Requests_Hooker $hooks Hooking system
*/ */
public function register(HookManager $hooks) { public function register(Requests_Hooker $hooks) {
$hooks->register('requests.before_request', [$this, 'before_request']); $hooks->register('requests.before_request', array($this, 'before_request'));
$hooks->register('requests.before_redirect_check', [$this, 'before_redirect_check']); $hooks->register('requests.before_redirect_check', array($this, 'before_redirect_check'));
} }
/** /**
@ -145,12 +134,12 @@ class Jar implements ArrayAccess, IteratorAggregate {
* @param array $options * @param array $options
*/ */
public function before_request($url, &$headers, &$data, &$type, &$options) { public function before_request($url, &$headers, &$data, &$type, &$options) {
if (!$url instanceof Iri) { if (!$url instanceof Requests_IRI) {
$url = new Iri($url); $url = new Requests_IRI($url);
} }
if (!empty($this->cookies)) { if (!empty($this->cookies)) {
$cookies = []; $cookies = array();
foreach ($this->cookies as $key => $cookie) { foreach ($this->cookies as $key => $cookie) {
$cookie = $this->normalize_cookie($cookie, $key); $cookie = $this->normalize_cookie($cookie, $key);
@ -171,16 +160,16 @@ class Jar implements ArrayAccess, IteratorAggregate {
/** /**
* Parse all cookies from a response and attach them to the response * Parse all cookies from a response and attach them to the response
* *
* @param \WpOrg\Requests\Response $response * @var Requests_Response $response
*/ */
public function before_redirect_check(Response $response) { public function before_redirect_check(Requests_Response $return) {
$url = $response->url; $url = $return->url;
if (!$url instanceof Iri) { if (!$url instanceof Requests_IRI) {
$url = new Iri($url); $url = new Requests_IRI($url);
} }
$cookies = Cookie::parse_from_headers($response->headers, $url); $cookies = Requests_Cookie::parse_from_headers($return->headers, $url);
$this->cookies = array_merge($this->cookies, $cookies); $this->cookies = array_merge($this->cookies, $cookies);
$response->cookies = $this; $return->cookies = $this;
} }
} }

View File

@ -2,19 +2,15 @@
/** /**
* Exception for HTTP requests * Exception for HTTP requests
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests;
use Exception as PHPException;
/** /**
* Exception for HTTP requests * Exception for HTTP requests
* *
* @package Requests\Exceptions * @package Requests
*/ */
class Exception extends PHPException { class Requests_Exception extends Exception {
/** /**
* Type of exception * Type of exception
* *
@ -45,7 +41,7 @@ class Exception extends PHPException {
} }
/** /**
* Like {@see \Exception::getCode()}, but a string code. * Like {@see getCode()}, but a string code.
* *
* @codeCoverageIgnore * @codeCoverageIgnore
* @return string * @return string

View File

@ -1,47 +0,0 @@
<?php
namespace WpOrg\Requests\Exception;
use 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 \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
);
}
}

View File

@ -2,20 +2,15 @@
/** /**
* Exception based on HTTP response * Exception based on HTTP response
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception;
use WpOrg\Requests\Exception;
use WpOrg\Requests\Exception\Http\StatusUnknown;
/** /**
* Exception based on HTTP response * Exception based on HTTP response
* *
* @package Requests\Exceptions * @package Requests
*/ */
class Http extends Exception { class Requests_Exception_HTTP extends Requests_Exception {
/** /**
* HTTP status code * HTTP status code
* *
@ -49,9 +44,7 @@ class Http extends Exception {
} }
/** /**
* Get the status message. * Get the status message
*
* @return string
*/ */
public function getReason() { public function getReason() {
return $this->reason; return $this->reason;
@ -65,14 +58,14 @@ class Http extends Exception {
*/ */
public static function get_class($code) { public static function get_class($code) {
if (!$code) { if (!$code) {
return StatusUnknown::class; return 'Requests_Exception_HTTP_Unknown';
} }
$class = sprintf('\WpOrg\Requests\Exception\Http\Status%d', $code); $class = sprintf('Requests_Exception_HTTP_%d', $code);
if (class_exists($class)) { if (class_exists($class)) {
return $class; return $class;
} }
return StatusUnknown::class; return 'Requests_Exception_HTTP_Unknown';
} }
} }

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 304 Not Modified responses * Exception for 304 Not Modified responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 304 Not Modified responses * Exception for 304 Not Modified responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status304 extends Http { class Requests_Exception_HTTP_304 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 305 Use Proxy responses * Exception for 305 Use Proxy responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 305 Use Proxy responses * Exception for 305 Use Proxy responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status305 extends Http { class Requests_Exception_HTTP_305 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 306 Switch Proxy responses * Exception for 306 Switch Proxy responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 306 Switch Proxy responses * Exception for 306 Switch Proxy responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status306 extends Http { class Requests_Exception_HTTP_306 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 400 Bad Request responses * Exception for 400 Bad Request responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 400 Bad Request responses * Exception for 400 Bad Request responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status400 extends Http { class Requests_Exception_HTTP_400 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 401 Unauthorized responses * Exception for 401 Unauthorized responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 401 Unauthorized responses * Exception for 401 Unauthorized responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status401 extends Http { class Requests_Exception_HTTP_401 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 402 Payment Required responses * Exception for 402 Payment Required responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 402 Payment Required responses * Exception for 402 Payment Required responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status402 extends Http { class Requests_Exception_HTTP_402 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 403 Forbidden responses * Exception for 403 Forbidden responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 403 Forbidden responses * Exception for 403 Forbidden responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status403 extends Http { class Requests_Exception_HTTP_403 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 404 Not Found responses * Exception for 404 Not Found responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 404 Not Found responses * Exception for 404 Not Found responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status404 extends Http { class Requests_Exception_HTTP_404 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 405 Method Not Allowed responses * Exception for 405 Method Not Allowed responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 405 Method Not Allowed responses * Exception for 405 Method Not Allowed responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status405 extends Http { class Requests_Exception_HTTP_405 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 406 Not Acceptable responses * Exception for 406 Not Acceptable responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 406 Not Acceptable responses * Exception for 406 Not Acceptable responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status406 extends Http { class Requests_Exception_HTTP_406 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 407 Proxy Authentication Required responses * Exception for 407 Proxy Authentication Required responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 407 Proxy Authentication Required responses * Exception for 407 Proxy Authentication Required responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status407 extends Http { class Requests_Exception_HTTP_407 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 408 Request Timeout responses * Exception for 408 Request Timeout responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 408 Request Timeout responses * Exception for 408 Request Timeout responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status408 extends Http { class Requests_Exception_HTTP_408 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 409 Conflict responses * Exception for 409 Conflict responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 409 Conflict responses * Exception for 409 Conflict responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status409 extends Http { class Requests_Exception_HTTP_409 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 410 Gone responses * Exception for 410 Gone responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 410 Gone responses * Exception for 410 Gone responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status410 extends Http { class Requests_Exception_HTTP_410 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 411 Length Required responses * Exception for 411 Length Required responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 411 Length Required responses * Exception for 411 Length Required responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status411 extends Http { class Requests_Exception_HTTP_411 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 412 Precondition Failed responses * Exception for 412 Precondition Failed responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 412 Precondition Failed responses * Exception for 412 Precondition Failed responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status412 extends Http { class Requests_Exception_HTTP_412 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 413 Request Entity Too Large responses * Exception for 413 Request Entity Too Large responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 413 Request Entity Too Large responses * Exception for 413 Request Entity Too Large responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status413 extends Http { class Requests_Exception_HTTP_413 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 414 Request-URI Too Large responses * Exception for 414 Request-URI Too Large responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 414 Request-URI Too Large responses * Exception for 414 Request-URI Too Large responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status414 extends Http { class Requests_Exception_HTTP_414 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 415 Unsupported Media Type responses * Exception for 415 Unsupported Media Type responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 415 Unsupported Media Type responses * Exception for 415 Unsupported Media Type responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status415 extends Http { class Requests_Exception_HTTP_415 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 416 Requested Range Not Satisfiable responses * Exception for 416 Requested Range Not Satisfiable responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 416 Requested Range Not Satisfiable responses * Exception for 416 Requested Range Not Satisfiable responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status416 extends Http { class Requests_Exception_HTTP_416 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 417 Expectation Failed responses * Exception for 417 Expectation Failed responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 417 Expectation Failed responses * Exception for 417 Expectation Failed responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status417 extends Http { class Requests_Exception_HTTP_417 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,23 +2,17 @@
/** /**
* Exception for 418 I'm A Teapot responses * Exception for 418 I'm A Teapot responses
* *
* @link https://tools.ietf.org/html/rfc2324 * @see https://tools.ietf.org/html/rfc2324
* * @package Requests
* @package Requests\Exceptions
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 418 I'm A Teapot responses * Exception for 418 I'm A Teapot responses
* *
* @link https://tools.ietf.org/html/rfc2324 * @see https://tools.ietf.org/html/rfc2324
* * @package Requests
* @package Requests\Exceptions
*/ */
final class Status418 extends Http { class Requests_Exception_HTTP_418 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,23 +2,17 @@
/** /**
* Exception for 428 Precondition Required responses * Exception for 428 Precondition Required responses
* *
* @link https://tools.ietf.org/html/rfc6585 * @see https://tools.ietf.org/html/rfc6585
* * @package Requests
* @package Requests\Exceptions
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 428 Precondition Required responses * Exception for 428 Precondition Required responses
* *
* @link https://tools.ietf.org/html/rfc6585 * @see https://tools.ietf.org/html/rfc6585
* * @package Requests
* @package Requests\Exceptions
*/ */
final class Status428 extends Http { class Requests_Exception_HTTP_428 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -0,0 +1,29 @@
<?php
/**
* Exception for 429 Too Many Requests responses
*
* @see https://tools.ietf.org/html/draft-nottingham-http-new-status-04
* @package Requests
*/
/**
* Exception for 429 Too Many Requests responses
*
* @see https://tools.ietf.org/html/draft-nottingham-http-new-status-04
* @package Requests
*/
class Requests_Exception_HTTP_429 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 429;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Too Many Requests';
}

View File

@ -2,23 +2,17 @@
/** /**
* Exception for 431 Request Header Fields Too Large responses * Exception for 431 Request Header Fields Too Large responses
* *
* @link https://tools.ietf.org/html/rfc6585 * @see https://tools.ietf.org/html/rfc6585
* * @package Requests
* @package Requests\Exceptions
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 431 Request Header Fields Too Large responses * Exception for 431 Request Header Fields Too Large responses
* *
* @link https://tools.ietf.org/html/rfc6585 * @see https://tools.ietf.org/html/rfc6585
* * @package Requests
* @package Requests\Exceptions
*/ */
final class Status431 extends Http { class Requests_Exception_HTTP_431 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 500 Internal Server Error responses * Exception for 500 Internal Server Error responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 500 Internal Server Error responses * Exception for 500 Internal Server Error responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status500 extends Http { class Requests_Exception_HTTP_500 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 501 Not Implemented responses * Exception for 501 Not Implemented responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 501 Not Implemented responses * Exception for 501 Not Implemented responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status501 extends Http { class Requests_Exception_HTTP_501 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 502 Bad Gateway responses * Exception for 502 Bad Gateway responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 502 Bad Gateway responses * Exception for 502 Bad Gateway responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status502 extends Http { class Requests_Exception_HTTP_502 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 503 Service Unavailable responses * Exception for 503 Service Unavailable responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 503 Service Unavailable responses * Exception for 503 Service Unavailable responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status503 extends Http { class Requests_Exception_HTTP_503 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 504 Gateway Timeout responses * Exception for 504 Gateway Timeout responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 504 Gateway Timeout responses * Exception for 504 Gateway Timeout responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status504 extends Http { class Requests_Exception_HTTP_504 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,19 +2,15 @@
/** /**
* Exception for 505 HTTP Version Not Supported responses * Exception for 505 HTTP Version Not Supported responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 505 HTTP Version Not Supported responses * Exception for 505 HTTP Version Not Supported responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class Status505 extends Http { class Requests_Exception_HTTP_505 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,23 +2,17 @@
/** /**
* Exception for 511 Network Authentication Required responses * Exception for 511 Network Authentication Required responses
* *
* @link https://tools.ietf.org/html/rfc6585 * @see https://tools.ietf.org/html/rfc6585
* * @package Requests
* @package Requests\Exceptions
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
/** /**
* Exception for 511 Network Authentication Required responses * Exception for 511 Network Authentication Required responses
* *
* @link https://tools.ietf.org/html/rfc6585 * @see https://tools.ietf.org/html/rfc6585
* * @package Requests
* @package Requests\Exceptions
*/ */
final class Status511 extends Http { class Requests_Exception_HTTP_511 extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *

View File

@ -2,20 +2,15 @@
/** /**
* Exception for unknown status responses * Exception for unknown status responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
namespace WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Response;
/** /**
* Exception for unknown status responses * Exception for unknown status responses
* *
* @package Requests\Exceptions * @package Requests
*/ */
final class StatusUnknown extends Http { class Requests_Exception_HTTP_Unknown extends Requests_Exception_HTTP {
/** /**
* HTTP status code * HTTP status code
* *
@ -33,15 +28,15 @@ final class StatusUnknown extends Http {
/** /**
* Create a new exception * Create a new exception
* *
* If `$data` is an instance of {@see \WpOrg\Requests\Response}, uses the status * If `$data` is an instance of {@see Requests_Response}, uses the status
* code from it. Otherwise, sets as 0 * code from it. Otherwise, sets as 0
* *
* @param string|null $reason Reason phrase * @param string|null $reason Reason phrase
* @param mixed $data Associated data * @param mixed $data Associated data
*/ */
public function __construct($reason = null, $data = null) { public function __construct($reason = null, $data = null) {
if ($data instanceof Response) { if ($data instanceof Requests_Response) {
$this->code = (int) $data->status_code; $this->code = $data->status_code;
} }
parent::__construct($reason, $data); parent::__construct($reason, $data);

View File

@ -1,35 +0,0 @@
<?php
/**
* Exception for 429 Too Many Requests responses
*
* @link https://tools.ietf.org/html/draft-nottingham-http-new-status-04
*
* @package Requests\Exceptions
*/
namespace WpOrg\Requests\Exception\Http;
use 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';
}

View File

@ -1,41 +0,0 @@
<?php
namespace 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 \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
)
);
}
}

View File

@ -1,17 +1,5 @@
<?php <?php
/**
* Transport Exception
*
* @package Requests\Exceptions
*/
namespace WpOrg\Requests\Exception; class Requests_Exception_Transport extends Requests_Exception {
use WpOrg\Requests\Exception; }
/**
* Transport Exception
*
* @package Requests\Exceptions
*/
class Transport extends Exception {}

View File

@ -1,20 +1,6 @@
<?php <?php
/**
* CURL Transport Exception.
*
* @package Requests\Exceptions
*/
namespace WpOrg\Requests\Exception\Transport; class Requests_Exception_Transport_cURL extends Requests_Exception_Transport {
use WpOrg\Requests\Exception\Transport;
/**
* CURL Transport Exception.
*
* @package Requests\Exceptions
*/
final class Curl extends Transport {
const EASY = 'cURLEasy'; const EASY = 'cURLEasy';
const MULTI = 'cURLMulti'; const MULTI = 'cURLMulti';
@ -43,21 +29,13 @@ final class Curl extends Transport {
*/ */
protected $reason = 'Unknown'; 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) { public function __construct($message, $type, $data = null, $code = 0) {
if ($type !== null) { if ($type !== null) {
$this->type = $type; $this->type = $type;
} }
if ($code !== null) { if ($code !== null) {
$this->code = (int) $code; $this->code = $code;
} }
if ($message !== null) { if ($message !== null) {
@ -69,9 +47,7 @@ final class Curl extends Transport {
} }
/** /**
* Get the error message. * Get the error message
*
* @return string
*/ */
public function getReason() { public function getReason() {
return $this->reason; return $this->reason;

View File

@ -2,22 +2,22 @@
/** /**
* Event dispatcher * Event dispatcher
* *
* @package Requests\EventDispatcher * @package Requests
* @subpackage Utilities
*/ */
namespace WpOrg\Requests;
/** /**
* Event dispatcher * Event dispatcher
* *
* @package Requests\EventDispatcher * @package Requests
* @subpackage Utilities
*/ */
interface HookManager { interface Requests_Hooker {
/** /**
* Register a callback for a hook * Register a callback for a hook
* *
* @param string $hook Hook name * @param string $hook Hook name
* @param callable $callback Function/method to call on event * @param callback $callback Function/method to call on event
* @param int $priority Priority number. <0 is executed earlier, >0 is executed later * @param int $priority Priority number. <0 is executed earlier, >0 is executed later
*/ */
public function register($hook, $callback, $priority = 0); public function register($hook, $callback, $priority = 0);
@ -29,5 +29,5 @@ interface HookManager {
* @param array $parameters Parameters to pass to callbacks * @param array $parameters Parameters to pass to callbacks
* @return boolean Successfulness * @return boolean Successfulness
*/ */
public function dispatch($hook, $parameters = []); public function dispatch($hook, $parameters = array());
} }

View File

@ -2,57 +2,44 @@
/** /**
* Handles adding and dispatching events * Handles adding and dispatching events
* *
* @package Requests\EventDispatcher * @package Requests
* @subpackage Utilities
*/ */
namespace WpOrg\Requests;
use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\HookManager;
use WpOrg\Requests\Utility\InputValidator;
/** /**
* Handles adding and dispatching events * Handles adding and dispatching events
* *
* @package Requests\EventDispatcher * @package Requests
* @subpackage Utilities
*/ */
class Hooks implements HookManager { class Requests_Hooks implements Requests_Hooker {
/** /**
* Registered callbacks for each hook * Registered callbacks for each hook
* *
* @var array * @var array
*/ */
protected $hooks = []; protected $hooks = array();
/**
* Constructor
*/
public function __construct() {
// pass
}
/** /**
* Register a callback for a hook * Register a callback for a hook
* *
* @param string $hook Hook name * @param string $hook Hook name
* @param callable $callback Function/method to call on event * @param callback $callback Function/method to call on event
* @param int $priority Priority number. <0 is executed earlier, >0 is executed later * @param int $priority Priority number. <0 is executed earlier, >0 is executed later
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $hook argument is not a string.
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $callback argument is not callable.
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $priority argument is not an integer.
*/ */
public function register($hook, $callback, $priority = 0) { 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])) { if (!isset($this->hooks[$hook])) {
$this->hooks[$hook] = [ $this->hooks[$hook] = array();
$priority => [], }
]; if (!isset($this->hooks[$hook][$priority])) {
} elseif (!isset($this->hooks[$hook][$priority])) { $this->hooks[$hook][$priority] = array();
$this->hooks[$hook][$priority] = [];
} }
$this->hooks[$hook][$priority][] = $callback; $this->hooks[$hook][$priority][] = $callback;
@ -64,31 +51,15 @@ class Hooks implements HookManager {
* @param string $hook Hook name * @param string $hook Hook name
* @param array $parameters Parameters to pass to callbacks * @param array $parameters Parameters to pass to callbacks
* @return boolean Successfulness * @return boolean Successfulness
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $hook argument is not a string.
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $parameters argument is not an array.
*/ */
public function dispatch($hook, $parameters = []) { public function dispatch($hook, $parameters = array()) {
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])) { if (empty($this->hooks[$hook])) {
return false; 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);
}
foreach ($this->hooks[$hook] as $priority => $hooked) { foreach ($this->hooks[$hook] as $priority => $hooked) {
foreach ($hooked as $callback) { foreach ($hooked as $callback) {
$callback(...$parameters); call_user_func_array($callback, $parameters);
} }
} }

View File

@ -1,45 +1,28 @@
<?php <?php
namespace WpOrg\Requests;
use WpOrg\Requests\Exception;
use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\Utility\InputValidator;
/** /**
* IDNA URL encoder * IDNA URL encoder
* *
* Note: Not fully compliant, as nameprep does nothing yet. * Note: Not fully compliant, as nameprep does nothing yet.
* *
* @package Requests\Utilities * @package Requests
* * @subpackage Utilities
* @link https://tools.ietf.org/html/rfc3490 IDNA specification * @see https://tools.ietf.org/html/rfc3490 IDNA specification
* @link https://tools.ietf.org/html/rfc3492 Punycode/Bootstrap specification * @see https://tools.ietf.org/html/rfc3492 Punycode/Bootstrap specification
*/ */
class IdnaEncoder { class Requests_IDNAEncoder {
/** /**
* ACE prefix used for IDNA * ACE prefix used for IDNA
* *
* @link https://tools.ietf.org/html/rfc3490#section-5 * @see https://tools.ietf.org/html/rfc3490#section-5
* @var string * @var string
*/ */
const ACE_PREFIX = 'xn--'; const ACE_PREFIX = 'xn--';
/**
* Maximum length of a IDNA URL in ASCII.
*
* @see \WpOrg\Requests\IdnaEncoder::to_ascii()
*
* @since 2.0.0
*
* @var int
*/
const MAX_LENGTH = 64;
/**#@+ /**#@+
* Bootstrap constant for Punycode * Bootstrap constant for Punycode
* *
* @link https://tools.ietf.org/html/rfc3492#section-5 * @see https://tools.ietf.org/html/rfc3492#section-5
* @var int * @var int
*/ */
const BOOTSTRAP_BASE = 36; const BOOTSTRAP_BASE = 36;
@ -54,16 +37,11 @@ class IdnaEncoder {
/** /**
* Encode a hostname using Punycode * Encode a hostname using Punycode
* *
* @param string|Stringable $hostname Hostname * @param string $string Hostname
* @return string Punycode-encoded hostname * @return string Punycode-encoded hostname
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string or a stringable object.
*/ */
public static function encode($hostname) { public static function encode($string) {
if (InputValidator::is_string_or_stringable($hostname) === false) { $parts = explode('.', $string);
throw InvalidArgument::create(1, '$hostname', 'string|Stringable', gettype($hostname));
}
$parts = explode('.', $hostname);
foreach ($parts as &$part) { foreach ($parts as &$part) {
$part = self::to_ascii($part); $part = self::to_ascii($part);
} }
@ -71,101 +49,94 @@ class IdnaEncoder {
} }
/** /**
* Convert a UTF-8 text string to an ASCII string using Punycode * Convert a UTF-8 string to an ASCII string using Punycode
* *
* @param string $text ASCII or UTF-8 string (max length 64 characters) * @throws Requests_Exception Provided string longer than 64 ASCII characters (`idna.provided_too_long`)
* @throws Requests_Exception Prepared string longer than 64 ASCII characters (`idna.prepared_too_long`)
* @throws Requests_Exception Provided string already begins with xn-- (`idna.provided_is_prefixed`)
* @throws Requests_Exception Encoded string longer than 64 ASCII characters (`idna.encoded_too_long`)
*
* @param string $string ASCII or UTF-8 string (max length 64 characters)
* @return string ASCII string * @return string ASCII string
*
* @throws \WpOrg\Requests\Exception Provided string longer than 64 ASCII characters (`idna.provided_too_long`)
* @throws \WpOrg\Requests\Exception Prepared string longer than 64 ASCII characters (`idna.prepared_too_long`)
* @throws \WpOrg\Requests\Exception Provided string already begins with xn-- (`idna.provided_is_prefixed`)
* @throws \WpOrg\Requests\Exception Encoded string longer than 64 ASCII characters (`idna.encoded_too_long`)
*/ */
public static function to_ascii($text) { public static function to_ascii($string) {
// Step 1: Check if the text is already ASCII // Step 1: Check if the string is already ASCII
if (self::is_ascii($text)) { if (self::is_ascii($string)) {
// Skip to step 7 // Skip to step 7
if (strlen($text) < self::MAX_LENGTH) { if (strlen($string) < 64) {
return $text; return $string;
} }
throw new Exception('Provided string is too long', 'idna.provided_too_long', $text); throw new Requests_Exception('Provided string is too long', 'idna.provided_too_long', $string);
} }
// Step 2: nameprep // Step 2: nameprep
$text = self::nameprep($text); $string = self::nameprep($string);
// Step 3: UseSTD3ASCIIRules is false, continue // Step 3: UseSTD3ASCIIRules is false, continue
// Step 4: Check if it's ASCII now // Step 4: Check if it's ASCII now
if (self::is_ascii($text)) { if (self::is_ascii($string)) {
// Skip to step 7 // Skip to step 7
/* if (strlen($string) < 64) {
* As the `nameprep()` method returns the original string, this code will never be reached until return $string;
* 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); throw new Requests_Exception('Prepared string is too long', 'idna.prepared_too_long', $string);
// @codeCoverageIgnoreEnd
} }
// Step 5: Check ACE prefix // Step 5: Check ACE prefix
if (strpos($text, self::ACE_PREFIX) === 0) { if (strpos($string, self::ACE_PREFIX) === 0) {
throw new Exception('Provided string begins with ACE prefix', 'idna.provided_is_prefixed', $text); throw new Requests_Exception('Provided string begins with ACE prefix', 'idna.provided_is_prefixed', $string);
} }
// Step 6: Encode with Punycode // Step 6: Encode with Punycode
$text = self::punycode_encode($text); $string = self::punycode_encode($string);
// Step 7: Prepend ACE prefix // Step 7: Prepend ACE prefix
$text = self::ACE_PREFIX . $text; $string = self::ACE_PREFIX . $string;
// Step 8: Check size // Step 8: Check size
if (strlen($text) < self::MAX_LENGTH) { if (strlen($string) < 64) {
return $text; return $string;
} }
throw new Exception('Encoded string is too long', 'idna.encoded_too_long', $text); throw new Requests_Exception('Encoded string is too long', 'idna.encoded_too_long', $string);
} }
/** /**
* Check whether a given text string contains only ASCII characters * Check whether a given string contains only ASCII characters
* *
* @internal (Testing found regex was the fastest implementation) * @internal (Testing found regex was the fastest implementation)
* *
* @param string $text * @param string $string
* @return bool Is the text string ASCII-only? * @return bool Is the string ASCII-only?
*/ */
protected static function is_ascii($text) { protected static function is_ascii($string) {
return (preg_match('/(?:[^\x00-\x7F])/', $text) !== 1); return (preg_match('/(?:[^\x00-\x7F])/', $string) !== 1);
} }
/** /**
* Prepare a text string for use as an IDNA name * Prepare a string for use as an IDNA name
* *
* @todo Implement this based on RFC 3491 and the newer 5891 * @todo Implement this based on RFC 3491 and the newer 5891
* @param string $text * @param string $string
* @return string Prepared string * @return string Prepared string
*/ */
protected static function nameprep($text) { protected static function nameprep($string) {
return $text; return $string;
} }
/** /**
* Convert a UTF-8 string to a UCS-4 codepoint array * Convert a UTF-8 string to a UCS-4 codepoint array
* *
* Based on \WpOrg\Requests\Iri::replace_invalid_with_pct_encoding() * Based on Requests_IRI::replace_invalid_with_pct_encoding()
* *
* @throws Requests_Exception Invalid UTF-8 codepoint (`idna.invalidcodepoint`)
* @param string $input * @param string $input
* @return array Unicode code points * @return array Unicode code points
*
* @throws \WpOrg\Requests\Exception Invalid UTF-8 codepoint (`idna.invalidcodepoint`)
*/ */
protected static function utf8_to_codepoints($input) { protected static function utf8_to_codepoints($input) {
$codepoints = []; $codepoints = array();
// Get number of bytes // Get number of bytes
$strlen = strlen($input); $strlen = strlen($input);
@ -200,19 +171,19 @@ class IdnaEncoder {
} }
// Invalid byte: // Invalid byte:
else { else {
throw new Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $value); throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $value);
} }
if ($remaining > 0) { if ($remaining > 0) {
if ($position + $length > $strlen) { if ($position + $length > $strlen) {
throw new Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character); throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
} }
for ($position++; $remaining > 0; $position++) { for ($position++; $remaining > 0; $position++) {
$value = ord($input[$position]); $value = ord($input[$position]);
// If it is invalid, count the sequence as invalid and reprocess the current byte: // If it is invalid, count the sequence as invalid and reprocess the current byte:
if (($value & 0xC0) !== 0x80) { if (($value & 0xC0) !== 0x80) {
throw new Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character); throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
} }
--$remaining; --$remaining;
@ -237,7 +208,7 @@ class IdnaEncoder {
|| $character > 0xEFFFD || $character > 0xEFFFD
) )
) { ) {
throw new Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character); throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
} }
$codepoints[] = $character; $codepoints[] = $character;
@ -250,11 +221,10 @@ class IdnaEncoder {
* RFC3492-compliant encoder * RFC3492-compliant encoder
* *
* @internal Pseudo-code from Section 6.3 is commented with "#" next to relevant code * @internal Pseudo-code from Section 6.3 is commented with "#" next to relevant code
* @throws Requests_Exception On character outside of the domain (never happens with Punycode) (`idna.character_outside_domain`)
* *
* @param string $input UTF-8 encoded string to encode * @param string $input UTF-8 encoded string to encode
* @return string Punycode-encoded string * @return string Punycode-encoded string
*
* @throws \WpOrg\Requests\Exception On character outside of the domain (never happens with Punycode) (`idna.character_outside_domain`)
*/ */
public static function punycode_encode($input) { public static function punycode_encode($input) {
$output = ''; $output = '';
@ -269,7 +239,7 @@ class IdnaEncoder {
$b = 0; // see loop $b = 0; // see loop
// copy them to the output in order // copy them to the output in order
$codepoints = self::utf8_to_codepoints($input); $codepoints = self::utf8_to_codepoints($input);
$extended = []; $extended = array();
foreach ($codepoints as $char) { foreach ($codepoints as $char) {
if ($char < 128) { if ($char < 128) {
@ -282,7 +252,7 @@ class IdnaEncoder {
// This never occurs for Punycode, so ignore in coverage // This never occurs for Punycode, so ignore in coverage
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
elseif ($char < $n) { elseif ($char < $n) {
throw new Exception('Invalid character', 'idna.character_outside_domain', $char); throw new Requests_Exception('Invalid character', 'idna.character_outside_domain', $char);
} }
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
else { else {
@ -362,18 +332,17 @@ class IdnaEncoder {
/** /**
* Convert a digit to its respective character * Convert a digit to its respective character
* *
* @link https://tools.ietf.org/html/rfc3492#section-5 * @see https://tools.ietf.org/html/rfc3492#section-5
* @throws Requests_Exception On invalid digit (`idna.invalid_digit`)
* *
* @param int $digit Digit in the range 0-35 * @param int $digit Digit in the range 0-35
* @return string Single character corresponding to digit * @return string Single character corresponding to digit
*
* @throws \WpOrg\Requests\Exception On invalid digit (`idna.invalid_digit`)
*/ */
protected static function digit_to_char($digit) { protected static function digit_to_char($digit) {
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
// As far as I know, this never happens, but still good to be sure. // As far as I know, this never happens, but still good to be sure.
if ($digit < 0 || $digit > 35) { if ($digit < 0 || $digit > 35) {
throw new Exception(sprintf('Invalid digit %d', $digit), 'idna.invalid_digit', $digit); throw new Requests_Exception(sprintf('Invalid digit %d', $digit), 'idna.invalid_digit', $digit);
} }
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
$digits = 'abcdefghijklmnopqrstuvwxyz0123456789'; $digits = 'abcdefghijklmnopqrstuvwxyz0123456789';
@ -383,7 +352,7 @@ class IdnaEncoder {
/** /**
* Adapt the bias * Adapt the bias
* *
* @link https://tools.ietf.org/html/rfc3492#section-6.1 * @see https://tools.ietf.org/html/rfc3492#section-6.1
* @param int $delta * @param int $delta
* @param int $numpoints * @param int $numpoints
* @param bool $firsttime * @param bool $firsttime

View File

@ -2,23 +2,20 @@
/** /**
* Class to validate and to work with IPv6 addresses * Class to validate and to work with IPv6 addresses
* *
* @package Requests\Utilities * @package Requests
* @subpackage Utilities
*/ */
namespace WpOrg\Requests;
use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\Utility\InputValidator;
/** /**
* Class to validate and to work with IPv6 addresses * Class to validate and to work with IPv6 addresses
* *
* This was originally based on the PEAR class of the same name, but has been * This was originally based on the PEAR class of the same name, but has been
* entirely rewritten. * entirely rewritten.
* *
* @package Requests\Utilities * @package Requests
* @subpackage Utilities
*/ */
final class Ipv6 { class Requests_IPv6 {
/** /**
* Uncompresses an IPv6 address * Uncompresses an IPv6 address
* *
@ -33,20 +30,11 @@ final class Ipv6 {
* @author elfrink at introweb dot nl * @author elfrink at introweb dot nl
* @author Josh Peck <jmp at joshpeck dot org> * @author Josh Peck <jmp at joshpeck dot org>
* @copyright 2003-2005 The PHP Group * @copyright 2003-2005 The PHP Group
* @license https://opensource.org/licenses/bsd-license.php * @license http://www.opensource.org/licenses/bsd-license.php
* * @param string $ip An IPv6 address
* @param string|Stringable $ip An IPv6 address
* @return string The uncompressed IPv6 address * @return string The uncompressed IPv6 address
*
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string or a stringable object.
*/ */
public static function uncompress($ip) { 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) { if (substr_count($ip, '::') !== 1) {
return $ip; return $ip;
} }
@ -90,14 +78,12 @@ final class Ipv6 {
* Example: FF01:0:0:0:0:0:0:101 -> FF01::101 * Example: FF01:0:0:0:0:0:0:101 -> FF01::101
* 0:0:0:0:0:0:0:1 -> ::1 * 0:0:0:0:0:0:0:1 -> ::1
* *
* @see \WpOrg\Requests\IPv6::uncompress() * @see uncompress()
*
* @param string $ip An IPv6 address * @param string $ip An IPv6 address
* @return string The compressed IPv6 address * @return string The compressed IPv6 address
*/ */
public static function compress($ip) { public static function compress($ip) {
// Prepare the IP to be compressed. // 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 = self::uncompress($ip);
$ip_parts = self::split_v6_v4($ip); $ip_parts = self::split_v6_v4($ip);
@ -138,15 +124,15 @@ final class Ipv6 {
* @param string $ip An IPv6 address * @param string $ip An IPv6 address
* @return string[] [0] contains the IPv6 represented part, and [1] the IPv4 represented part * @return string[] [0] contains the IPv6 represented part, and [1] the IPv4 represented part
*/ */
private static function split_v6_v4($ip) { protected static function split_v6_v4($ip) {
if (strpos($ip, '.') !== false) { if (strpos($ip, '.') !== false) {
$pos = strrpos($ip, ':'); $pos = strrpos($ip, ':');
$ipv6_part = substr($ip, 0, $pos); $ipv6_part = substr($ip, 0, $pos);
$ipv4_part = substr($ip, $pos + 1); $ipv4_part = substr($ip, $pos + 1);
return [$ipv6_part, $ipv4_part]; return array($ipv6_part, $ipv4_part);
} }
else { else {
return [$ip, '']; return array($ip, '');
} }
} }
@ -159,7 +145,6 @@ final class Ipv6 {
* @return bool true if $ip is a valid IPv6 address * @return bool true if $ip is a valid IPv6 address
*/ */
public static function check_ipv6($ip) { 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); $ip = self::uncompress($ip);
list($ipv6, $ipv4) = self::split_v6_v4($ip); list($ipv6, $ipv4) = self::split_v6_v4($ip);
$ipv6 = explode(':', $ipv6); $ipv6 = explode(':', $ipv6);

View File

@ -2,17 +2,10 @@
/** /**
* IRI parser/serialiser/normaliser * IRI parser/serialiser/normaliser
* *
* @package Requests\Utilities * @package Requests
* @subpackage Utilities
*/ */
namespace WpOrg\Requests;
use WpOrg\Requests\Exception;
use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\Ipv6;
use WpOrg\Requests\Port;
use WpOrg\Requests\Utility\InputValidator;
/** /**
* IRI parser/serialiser/normaliser * IRI parser/serialiser/normaliser
* *
@ -45,15 +38,16 @@ use WpOrg\Requests\Utility\InputValidator;
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE. * POSSIBILITY OF SUCH DAMAGE.
* *
* @package Requests\Utilities * @package Requests
* @subpackage Utilities
* @author Geoffrey Sneddon * @author Geoffrey Sneddon
* @author Steve Minutillo * @author Steve Minutillo
* @copyright 2007-2009 Geoffrey Sneddon and Steve Minutillo * @copyright 2007-2009 Geoffrey Sneddon and Steve Minutillo
* @license https://opensource.org/licenses/bsd-license.php * @license http://www.opensource.org/licenses/bsd-license.php
* @link http://hg.gsnedders.com/iri/ * @link http://hg.gsnedders.com/iri/
* *
* @property string $iri IRI we're working with * @property string $iri IRI we're working with
* @property-read string $uri IRI in URI form, {@see \WpOrg\Requests\IRI::to_uri()} * @property-read string $uri IRI in URI form, {@see to_uri}
* @property string $scheme Scheme part of the IRI * @property string $scheme Scheme part of the IRI
* @property string $authority Authority part, formatted for a URI (userinfo + host + port) * @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 $iauthority Authority part of the IRI (userinfo + host + port)
@ -69,7 +63,7 @@ use WpOrg\Requests\Utility\InputValidator;
* @property string $fragment Fragment, formatted for a URI (after '#') * @property string $fragment Fragment, formatted for a URI (after '#')
* @property string $ifragment Fragment part of the IRI (after '#') * @property string $ifragment Fragment part of the IRI (after '#')
*/ */
class Iri { class Requests_IRI {
/** /**
* Scheme * Scheme
* *
@ -129,19 +123,19 @@ class Iri {
*/ */
protected $normalization = array( protected $normalization = array(
'acap' => array( 'acap' => array(
'port' => Port::ACAP, 'port' => 674
), ),
'dict' => array( 'dict' => array(
'port' => Port::DICT, 'port' => 2628
), ),
'file' => array( 'file' => array(
'ihost' => 'localhost', 'ihost' => 'localhost'
), ),
'http' => array( 'http' => array(
'port' => Port::HTTP, 'port' => 80,
), ),
'https' => array( 'https' => array(
'port' => Port::HTTPS, 'port' => 443,
), ),
); );
@ -246,15 +240,9 @@ class Iri {
/** /**
* Create a new IRI object, from a specified string * Create a new IRI object, from a specified string
* *
* @param string|Stringable|null $iri * @param string|null $iri
*
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $iri argument is not a string, Stringable or null.
*/ */
public function __construct($iri = 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); $this->set_iri($iri);
} }
@ -263,13 +251,13 @@ class Iri {
* *
* Returns false if $base is not absolute, otherwise an IRI. * Returns false if $base is not absolute, otherwise an IRI.
* *
* @param \WpOrg\Requests\Iri|string $base (Absolute) Base IRI * @param Requests_IRI|string $base (Absolute) Base IRI
* @param \WpOrg\Requests\Iri|string $relative Relative IRI * @param Requests_IRI|string $relative Relative IRI
* @return \WpOrg\Requests\Iri|false * @return Requests_IRI|false
*/ */
public static function absolutize($base, $relative) { public static function absolutize($base, $relative) {
if (!($relative instanceof self)) { if (!($relative instanceof Requests_IRI)) {
$relative = new self($relative); $relative = new Requests_IRI($relative);
} }
if (!$relative->is_valid()) { if (!$relative->is_valid()) {
return false; return false;
@ -278,8 +266,8 @@ class Iri {
return clone $relative; return clone $relative;
} }
if (!($base instanceof self)) { if (!($base instanceof Requests_IRI)) {
$base = new self($base); $base = new Requests_IRI($base);
} }
if ($base->scheme === null || !$base->is_valid()) { if ($base->scheme === null || !$base->is_valid()) {
return false; return false;
@ -291,7 +279,7 @@ class Iri {
$target->scheme = $base->scheme; $target->scheme = $base->scheme;
} }
else { else {
$target = new self; $target = new Requests_IRI;
$target->scheme = $base->scheme; $target->scheme = $base->scheme;
$target->iuserinfo = $base->iuserinfo; $target->iuserinfo = $base->iuserinfo;
$target->ihost = $base->ihost; $target->ihost = $base->ihost;
@ -342,7 +330,7 @@ class Iri {
$iri = trim($iri, "\x20\x09\x0A\x0C\x0D"); $iri = trim($iri, "\x20\x09\x0A\x0C\x0D");
$has_match = preg_match('/^((?P<scheme>[^:\/?#]+):)?(\/\/(?P<authority>[^\/?#]*))?(?P<path>[^?#]*)(\?(?P<query>[^#]*))?(#(?P<fragment>.*))?$/', $iri, $match); $has_match = preg_match('/^((?P<scheme>[^:\/?#]+):)?(\/\/(?P<authority>[^\/?#]*))?(?P<path>[^?#]*)(\?(?P<query>[^#]*))?(#(?P<fragment>.*))?$/', $iri, $match);
if (!$has_match) { if (!$has_match) {
throw new Exception('Cannot parse supplied IRI', 'iri.cannot_parse', $iri); throw new Requests_Exception('Cannot parse supplied IRI', 'iri.cannot_parse', $iri);
} }
if ($match[1] === '') { if ($match[1] === '') {
@ -425,18 +413,18 @@ class Iri {
/** /**
* Replace invalid character with percent encoding * Replace invalid character with percent encoding
* *
* @param string $text Input string * @param string $string Input string
* @param string $extra_chars Valid characters not in iunreserved or * @param string $extra_chars Valid characters not in iunreserved or
* iprivate (this is ASCII-only) * iprivate (this is ASCII-only)
* @param bool $iprivate Allow iprivate * @param bool $iprivate Allow iprivate
* @return string * @return string
*/ */
protected function replace_invalid_with_pct_encoding($text, $extra_chars, $iprivate = false) { protected function replace_invalid_with_pct_encoding($string, $extra_chars, $iprivate = false) {
// Normalize as many pct-encoded sections as possible // Normalize as many pct-encoded sections as possible
$text = preg_replace_callback('/(?:%[A-Fa-f0-9]{2})+/', array($this, 'remove_iunreserved_percent_encoded'), $text); $string = preg_replace_callback('/(?:%[A-Fa-f0-9]{2})+/', array($this, 'remove_iunreserved_percent_encoded'), $string);
// Replace invalid percent characters // Replace invalid percent characters
$text = preg_replace('/%(?![A-Fa-f0-9]{2})/', '%25', $text); $string = preg_replace('/%(?![A-Fa-f0-9]{2})/', '%25', $string);
// Add unreserved and % to $extra_chars (the latter is safe because all // Add unreserved and % to $extra_chars (the latter is safe because all
// pct-encoded sections are now valid). // pct-encoded sections are now valid).
@ -444,9 +432,9 @@ class Iri {
// Now replace any bytes that aren't allowed with their pct-encoded versions // Now replace any bytes that aren't allowed with their pct-encoded versions
$position = 0; $position = 0;
$strlen = strlen($text); $strlen = strlen($string);
while (($position += strspn($text, $extra_chars, $position)) < $strlen) { while (($position += strspn($string, $extra_chars, $position)) < $strlen) {
$value = ord($text[$position]); $value = ord($string[$position]);
// Start position // Start position
$start = $position; $start = $position;
@ -483,7 +471,7 @@ class Iri {
if ($remaining) { if ($remaining) {
if ($position + $length <= $strlen) { if ($position + $length <= $strlen) {
for ($position++; $remaining; $position++) { for ($position++; $remaining; $position++) {
$value = ord($text[$position]); $value = ord($string[$position]);
// Check that the byte is valid, then add it to the character: // Check that the byte is valid, then add it to the character:
if (($value & 0xC0) === 0x80) { if (($value & 0xC0) === 0x80) {
@ -534,7 +522,7 @@ class Iri {
} }
for ($j = $start; $j <= $position; $j++) { for ($j = $start; $j <= $position; $j++) {
$text = substr_replace($text, sprintf('%%%02X', ord($text[$j])), $j, 1); $string = substr_replace($string, sprintf('%%%02X', ord($string[$j])), $j, 1);
$j += 2; $j += 2;
$position += 2; $position += 2;
$strlen += 2; $strlen += 2;
@ -542,7 +530,7 @@ class Iri {
} }
} }
return $text; return $string;
} }
/** /**
@ -551,13 +539,13 @@ class Iri {
* Removes sequences of percent encoded bytes that represent UTF-8 * Removes sequences of percent encoded bytes that represent UTF-8
* encoded characters in iunreserved * encoded characters in iunreserved
* *
* @param array $regex_match PCRE match * @param array $match PCRE match
* @return string Replacement * @return string Replacement
*/ */
protected function remove_iunreserved_percent_encoded($regex_match) { protected function remove_iunreserved_percent_encoded($match) {
// As we just have valid percent encoded sequences we can just explode // As we just have valid percent encoded sequences we can just explode
// and ignore the first member of the returned array (an empty string). // and ignore the first member of the returned array (an empty string).
$bytes = explode('%', $regex_match[0]); $bytes = explode('%', $match[0]);
// Initialize the new string (this is what will be returned) and that // Initialize the new string (this is what will be returned) and that
// there are no bytes remaining in the current sequence (unsurprising // there are no bytes remaining in the current sequence (unsurprising
@ -733,9 +721,6 @@ class Iri {
if ($iri === null) { if ($iri === null) {
return true; return true;
} }
$iri = (string) $iri;
if (isset($cache[$iri])) { if (isset($cache[$iri])) {
list($this->scheme, list($this->scheme,
$this->iuserinfo, $this->iuserinfo,
@ -748,7 +733,7 @@ class Iri {
return $return; return $return;
} }
$parsed = $this->parse_iri($iri); $parsed = $this->parse_iri((string) $iri);
$return = $this->set_scheme($parsed['scheme']) $return = $this->set_scheme($parsed['scheme'])
&& $this->set_authority($parsed['authority']) && $this->set_authority($parsed['authority'])
@ -878,8 +863,8 @@ class Iri {
return true; return true;
} }
if (substr($ihost, 0, 1) === '[' && substr($ihost, -1) === ']') { if (substr($ihost, 0, 1) === '[' && substr($ihost, -1) === ']') {
if (Ipv6::check_ipv6(substr($ihost, 1, -1))) { if (Requests_IPv6::check_ipv6(substr($ihost, 1, -1))) {
$this->ihost = '[' . Ipv6::compress(substr($ihost, 1, -1)) . ']'; $this->ihost = '[' . Requests_IPv6::compress(substr($ihost, 1, -1)) . ']';
} }
else { else {
$this->ihost = null; $this->ihost = null;
@ -1000,11 +985,11 @@ class Iri {
/** /**
* Convert an IRI to a URI (or parts thereof) * 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()}) * @param string|bool IRI to convert (or false from {@see get_iri})
* @return string|false URI if IRI is valid, false otherwise. * @return string|false URI if IRI is valid, false otherwise.
*/ */
protected function to_uri($iri) { protected function to_uri($string) {
if (!is_string($iri)) { if (!is_string($string)) {
return false; return false;
} }
@ -1014,14 +999,14 @@ class Iri {
} }
$position = 0; $position = 0;
$strlen = strlen($iri); $strlen = strlen($string);
while (($position += strcspn($iri, $non_ascii, $position)) < $strlen) { while (($position += strcspn($string, $non_ascii, $position)) < $strlen) {
$iri = substr_replace($iri, sprintf('%%%02X', ord($iri[$position])), $position, 1); $string = substr_replace($string, sprintf('%%%02X', ord($string[$position])), $position, 1);
$position += 3; $position += 3;
$strlen += 2; $strlen += 2;
} }
return $iri; return $string;
} }
/** /**

View File

@ -1,75 +0,0 @@
<?php
/**
* Port utilities for Requests
*
* @package Requests\Utilities
* @since 2.0.0
*/
namespace WpOrg\Requests;
use WpOrg\Requests\Exception;
use 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 \WpOrg\Requests\Exception\InvalidArgument When a non-string input has been passed.
* @throws \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}");
}
}

View File

@ -2,14 +2,11 @@
/** /**
* Proxy connection interface * Proxy connection interface
* *
* @package Requests\Proxy * @package Requests
* @subpackage Proxy
* @since 1.6 * @since 1.6
*/ */
namespace WpOrg\Requests;
use WpOrg\Requests\Hooks;
/** /**
* Proxy connection interface * Proxy connection interface
* *
@ -18,21 +15,21 @@ use WpOrg\Requests\Hooks;
* Parameters should be passed via the constructor where possible, as this * Parameters should be passed via the constructor where possible, as this
* makes it much easier for users to use your provider. * makes it much easier for users to use your provider.
* *
* @see \WpOrg\Requests\Hooks * @see Requests_Hooks
* * @package Requests
* @package Requests\Proxy * @subpackage Proxy
* @since 1.6 * @since 1.6
*/ */
interface Proxy { interface Requests_Proxy {
/** /**
* Register hooks as needed * Register hooks as needed
* *
* This method is called in {@see \WpOrg\Requests\Requests::request()} when the user * This method is called in {@see Requests::request} when the user has set
* has set an instance as the 'auth' option. Use this callback to register all the * an instance as the 'auth' option. Use this callback to register all the
* hooks you'll need. * hooks you'll need.
* *
* @see \WpOrg\Requests\Hooks::register() * @see Requests_Hooks::register
* @param \WpOrg\Requests\Hooks $hooks Hook system * @param Requests_Hooks $hooks Hook system
*/ */
public function register(Hooks $hooks); public function register(Requests_Hooks $hooks);
} }

View File

@ -2,26 +2,21 @@
/** /**
* HTTP Proxy connection interface * HTTP Proxy connection interface
* *
* @package Requests\Proxy * @package Requests
* @subpackage Proxy
* @since 1.6 * @since 1.6
*/ */
namespace WpOrg\Requests\Proxy;
use WpOrg\Requests\Exception\ArgumentCount;
use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\Hooks;
use WpOrg\Requests\Proxy;
/** /**
* HTTP Proxy connection interface * HTTP Proxy connection interface
* *
* Provides a handler for connection via an HTTP proxy * Provides a handler for connection via an HTTP proxy
* *
* @package Requests\Proxy * @package Requests
* @subpackage Proxy
* @since 1.6 * @since 1.6
*/ */
final class Http implements Proxy { class Requests_Proxy_HTTP implements Requests_Proxy {
/** /**
* Proxy host and port * Proxy host and port
* *
@ -56,13 +51,8 @@ final class Http implements Proxy {
* Constructor * Constructor
* *
* @since 1.6 * @since 1.6
* * @throws Requests_Exception On incorrect number of arguments (`authbasicbadargs`)
* @param array|string|null $args Proxy as a string or an array of proxy, user and password. * @param array|null $args Array of user and password. Must have exactly two elements
* When passed as an array, must have exactly one (proxy)
* or three elements (proxy, user, password).
*
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not an array, a string or null.
* @throws \WpOrg\Requests\Exception\ArgumentCount On incorrect number of arguments (`proxyhttpbadargs`)
*/ */
public function __construct($args = null) { public function __construct($args = null) {
if (is_string($args)) { if (is_string($args)) {
@ -77,14 +67,8 @@ final class Http implements Proxy {
$this->use_authentication = true; $this->use_authentication = true;
} }
else { else {
throw ArgumentCount::create( throw new Requests_Exception('Invalid number of arguments', 'proxyhttpbadargs');
'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));
} }
} }
@ -92,19 +76,19 @@ final class Http implements Proxy {
* Register the necessary callbacks * Register the necessary callbacks
* *
* @since 1.6 * @since 1.6
* @see \WpOrg\Requests\Proxy\HTTP::curl_before_send() * @see curl_before_send
* @see \WpOrg\Requests\Proxy\HTTP::fsockopen_remote_socket() * @see fsockopen_remote_socket
* @see \WpOrg\Requests\Proxy\HTTP::fsockopen_remote_host_path() * @see fsockopen_remote_host_path
* @see \WpOrg\Requests\Proxy\HTTP::fsockopen_header() * @see fsockopen_header
* @param \WpOrg\Requests\Hooks $hooks Hook system * @param Requests_Hooks $hooks Hook system
*/ */
public function register(Hooks $hooks) { public function register(Requests_Hooks $hooks) {
$hooks->register('curl.before_send', [$this, 'curl_before_send']); $hooks->register('curl.before_send', array($this, 'curl_before_send'));
$hooks->register('fsockopen.remote_socket', [$this, 'fsockopen_remote_socket']); $hooks->register('fsockopen.remote_socket', array($this, 'fsockopen_remote_socket'));
$hooks->register('fsockopen.remote_host_path', [$this, 'fsockopen_remote_host_path']); $hooks->register('fsockopen.remote_host_path', array($this, 'fsockopen_remote_host_path'));
if ($this->use_authentication) { if ($this->use_authentication) {
$hooks->register('fsockopen.after_headers', [$this, 'fsockopen_header']); $hooks->register('fsockopen.after_headers', array($this, 'fsockopen_header'));
} }
} }
@ -112,7 +96,7 @@ final class Http implements Proxy {
* Set cURL parameters before the data is sent * Set cURL parameters before the data is sent
* *
* @since 1.6 * @since 1.6
* @param resource|\CurlHandle $handle cURL handle * @param resource $handle cURL resource
*/ */
public function curl_before_send(&$handle) { public function curl_before_send(&$handle) {
curl_setopt($handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); curl_setopt($handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);

File diff suppressed because it is too large Load Diff

View File

@ -2,26 +2,24 @@
/** /**
* HTTP response class * HTTP response class
* *
* Contains a response from \WpOrg\Requests\Requests::request() * Contains a response from Requests::request()
*
* @package Requests * @package Requests
*/ */
namespace WpOrg\Requests;
use WpOrg\Requests\Cookie\Jar;
use WpOrg\Requests\Exception;
use WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Response\Headers;
/** /**
* HTTP response class * HTTP response class
* *
* Contains a response from \WpOrg\Requests\Requests::request() * Contains a response from Requests::request()
*
* @package Requests * @package Requests
*/ */
class Response { class Requests_Response {
/**
* Constructor
*/
public function __construct() {
$this->headers = new Requests_Response_Headers();
$this->cookies = new Requests_Cookie_Jar();
}
/** /**
* Response body * Response body
@ -40,9 +38,9 @@ class Response {
/** /**
* Headers, as an associative array * Headers, as an associative array
* *
* @var \WpOrg\Requests\Response\Headers Array-like object representing headers * @var Requests_Response_Headers Array-like object representing headers
*/ */
public $headers = []; public $headers = array();
/** /**
* Status code, false if non-blocking * Status code, false if non-blocking
@ -82,24 +80,16 @@ class Response {
/** /**
* Previous requests (from redirects) * Previous requests (from redirects)
* *
* @var array Array of \WpOrg\Requests\Response objects * @var array Array of Requests_Response objects
*/ */
public $history = []; public $history = array();
/** /**
* Cookies from the request * Cookies from the request
* *
* @var \WpOrg\Requests\Cookie\Jar Array-like object representing a cookie jar * @var Requests_Cookie_Jar Array-like object representing a cookie jar
*/ */
public $cookies = []; public $cookies = array();
/**
* Constructor
*/
public function __construct() {
$this->headers = new Headers();
$this->cookies = new Jar();
}
/** /**
* Is the response a redirect? * Is the response a redirect?
@ -108,59 +98,25 @@ class Response {
*/ */
public function is_redirect() { public function is_redirect() {
$code = $this->status_code; $code = $this->status_code;
return in_array($code, [300, 301, 302, 303, 307], true) || $code > 307 && $code < 400; return in_array($code, array(300, 301, 302, 303, 307), true) || $code > 307 && $code < 400;
} }
/** /**
* Throws an exception if the request was not successful * Throws an exception if the request was not successful
* *
* @throws Requests_Exception If `$allow_redirects` is false, and code is 3xx (`response.no_redirects`)
* @throws Requests_Exception_HTTP On non-successful status code. Exception class corresponds to code (e.g. {@see Requests_Exception_HTTP_404})
* @param boolean $allow_redirects Set to false to throw on a 3xx as well * @param boolean $allow_redirects Set to false to throw on a 3xx as well
*
* @throws \WpOrg\Requests\Exception If `$allow_redirects` is false, and code is 3xx (`response.no_redirects`)
* @throws \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) { public function throw_for_status($allow_redirects = true) {
if ($this->is_redirect()) { if ($this->is_redirect()) {
if ($allow_redirects !== true) { if (!$allow_redirects) {
throw new Exception('Redirection not allowed', 'response.no_redirects', $this); throw new Requests_Exception('Redirection not allowed', 'response.no_redirects', $this);
} }
} }
elseif (!$this->success) { elseif (!$this->success) {
$exception = Http::get_class($this->status_code); $exception = Requests_Exception_HTTP::get_class($this->status_code);
throw new $exception(null, $this); 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 $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 \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;
}
} }

View File

@ -5,86 +5,68 @@
* @package Requests * @package Requests
*/ */
namespace WpOrg\Requests\Response;
use WpOrg\Requests\Exception;
use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\Utility\CaseInsensitiveDictionary;
use WpOrg\Requests\Utility\FilteredIterator;
/** /**
* Case-insensitive dictionary, suitable for HTTP headers * Case-insensitive dictionary, suitable for HTTP headers
* *
* @package Requests * @package Requests
*/ */
class Headers extends CaseInsensitiveDictionary { class Requests_Response_Headers extends Requests_Utility_CaseInsensitiveDictionary {
/** /**
* Get the given header * Get the given header
* *
* Unlike {@see \WpOrg\Requests\Response\Headers::getValues()}, this returns a string. If there are * Unlike {@see self::getValues()}, this returns a string. If there are
* multiple values, it concatenates them with a comma as per RFC2616. * multiple values, it concatenates them with a comma as per RFC2616.
* *
* Avoid using this where commas may be used unquoted in values, such as * Avoid using this where commas may be used unquoted in values, such as
* Set-Cookie headers. * Set-Cookie headers.
* *
* @param string $offset * @param string $key
* @return string|null Header value * @return string|null Header value
*/ */
public function offsetGet($offset) { public function offsetGet($key) {
if (is_string($offset)) { $key = strtolower($key);
$offset = strtolower($offset); if (!isset($this->data[$key])) {
}
if (!isset($this->data[$offset])) {
return null; return null;
} }
return $this->flatten($this->data[$offset]); return $this->flatten($this->data[$key]);
} }
/** /**
* Set the given item * Set the given item
* *
* @param string $offset Item name * @throws Requests_Exception On attempting to use dictionary as list (`invalidset`)
* @param string $value Item value
* *
* @throws \WpOrg\Requests\Exception On attempting to use dictionary as list (`invalidset`) * @param string $key Item name
* @param string $value Item value
*/ */
public function offsetSet($offset, $value) { public function offsetSet($key, $value) {
if ($offset === null) { if ($key === null) {
throw new Exception('Object is a dictionary, not a list', 'invalidset'); throw new Requests_Exception('Object is a dictionary, not a list', 'invalidset');
} }
if (is_string($offset)) { $key = strtolower($key);
$offset = strtolower($offset);
if (!isset($this->data[$key])) {
$this->data[$key] = array();
} }
if (!isset($this->data[$offset])) { $this->data[$key][] = $value;
$this->data[$offset] = [];
}
$this->data[$offset][] = $value;
} }
/** /**
* Get all values for a given header * Get all values for a given header
* *
* @param string $offset * @param string $key
* @return array|null Header values * @return array|null Header values
*
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not valid as an array key.
*/ */
public function getValues($offset) { public function getValues($key) {
if (!is_string($offset) && !is_int($offset)) { $key = strtolower($key);
throw InvalidArgument::create(1, '$offset', 'string|int', gettype($offset)); if (!isset($this->data[$key])) {
}
$offset = strtolower($offset);
if (!isset($this->data[$offset])) {
return null; return null;
} }
return $this->data[$offset]; return $this->data[$key];
} }
/** /**
@ -95,30 +77,22 @@ class Headers extends CaseInsensitiveDictionary {
* *
* @param string|array $value Value to flatten * @param string|array $value Value to flatten
* @return string Flattened value * @return string Flattened value
*
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string or an array.
*/ */
public function flatten($value) { public function flatten($value) {
if (is_string($value)) {
return $value;
}
if (is_array($value)) { if (is_array($value)) {
return implode(',', $value); $value = implode(',', $value);
} }
throw InvalidArgument::create(1, '$value', 'string|array', gettype($value)); return $value;
} }
/** /**
* Get an iterator for the data * Get an iterator for the data
* *
* Converts the internally stored values to a comma-separated string if there is more * Converts the internal
* than one value for a key. * @return ArrayIterator
*
* @return \ArrayIterator
*/ */
public function getIterator() { public function getIterator() {
return new FilteredIterator($this->data, [$this, 'flatten']); return new Requests_Utility_FilteredIterator($this->data, array($this, 'flatten'));
} }
} }

View File

@ -2,49 +2,37 @@
/** /**
* SSL utilities for Requests * SSL utilities for Requests
* *
* @package Requests\Utilities * @package Requests
* @subpackage Utilities
*/ */
namespace WpOrg\Requests;
use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\Utility\InputValidator;
/** /**
* SSL utilities for Requests * SSL utilities for Requests
* *
* Collection of utilities for working with and verifying SSL certificates. * Collection of utilities for working with and verifying SSL certificates.
* *
* @package Requests\Utilities * @package Requests
* @subpackage Utilities
*/ */
final class Ssl { class Requests_SSL {
/** /**
* Verify the certificate against common name and subject alternative names * Verify the certificate against common name and subject alternative names
* *
* Unfortunately, PHP doesn't check the certificate against the alternative * Unfortunately, PHP doesn't check the certificate against the alternative
* names, leading things like 'https://www.github.com/' to be invalid. * 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 * @see https://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1
* *
* @param string|Stringable $host Host name to verify against * @throws Requests_Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`)
* @param string $host Host name to verify against
* @param array $cert Certificate data from openssl_x509_parse() * @param array $cert Certificate data from openssl_x509_parse()
* @return bool * @return bool
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $host argument is not a string or a stringable object.
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $cert argument is not an array or array accessible.
*/ */
public static function verify_certificate($host, $cert) { 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; $has_dns_alt = false;
// Check the subjectAltName // Check the subjectAltName
if (!empty($cert['extensions']['subjectAltName'])) { if (!empty($cert['extensions']) && !empty($cert['extensions']['subjectAltName'])) {
$altnames = explode(',', $cert['extensions']['subjectAltName']); $altnames = explode(',', $cert['extensions']['subjectAltName']);
foreach ($altnames as $altname) { foreach ($altnames as $altname) {
$altname = trim($altname); $altname = trim($altname);
@ -62,17 +50,15 @@ final class Ssl {
return true; return true;
} }
} }
if ($has_dns_alt === true) {
return false;
}
} }
// Fall back to checking the common name if we didn't get any dNSName // Fall back to checking the common name if we didn't get any dNSName
// alt names, as per RFC2818 // alt names, as per RFC2818
if (!empty($cert['subject']['CN'])) { if (!$has_dns_alt && !empty($cert['subject']['CN'])) {
// Check for a match // Check for a match
return (self::match_domain($host, $cert['subject']['CN']) === true); if (self::match_domain($host, $cert['subject']['CN']) === true) {
return true;
}
} }
return false; return false;
@ -91,29 +77,11 @@ final class Ssl {
* character to be the full first component; that is, with the exclusion of * character to be the full first component; that is, with the exclusion of
* the third rule. * the third rule.
* *
* @param string|Stringable $reference Reference dNSName * @param string $reference Reference dNSName
* @return boolean Is the name valid? * @return boolean Is the name valid?
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string or a stringable object.
*/ */
public static function verify_reference_name($reference) { 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); $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 // Check the first part of the name
$first = array_shift($parts); $first = array_shift($parts);
@ -144,35 +112,29 @@ final class Ssl {
/** /**
* Match a hostname against a dNSName reference * Match a hostname against a dNSName reference
* *
* @param string|Stringable $host Requested host * @param string $host Requested host
* @param string|Stringable $reference dNSName to match against * @param string $reference dNSName to match against
* @return boolean Does the domain match? * @return boolean Does the domain match?
* @throws \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) { 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 // Check if the reference is blocklisted first
if (self::verify_reference_name($reference) !== true) { if (self::verify_reference_name($reference) !== true) {
return false; return false;
} }
// Check for a direct match // Check for a direct match
if ((string) $host === (string) $reference) { if ($host === $reference) {
return true; return true;
} }
// Calculate the valid wildcard match if the host is not an IP address // 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, // Also validates that the host has 3 parts or more, as per Firefox's
// as a wildcard reference is only allowed with 3 parts or more, so the // ruleset.
// comparison will never match if host doesn't contain 3 parts or more as well.
if (ip2long($host) === false) { if (ip2long($host) === false) {
$parts = explode('.', $host); $parts = explode('.', $host);
$parts[0] = '*'; $parts[0] = '*';
$wildcard = implode('.', $parts); $wildcard = implode('.', $parts);
if ($wildcard === (string) $reference) { if ($wildcard === $reference) {
return true; return true;
} }
} }

View File

@ -2,17 +2,10 @@
/** /**
* Session handler for persistent requests and default parameters * Session handler for persistent requests and default parameters
* *
* @package Requests\SessionHandler * @package Requests
* @subpackage Session Handler
*/ */
namespace WpOrg\Requests;
use WpOrg\Requests\Cookie\Jar;
use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\Iri;
use WpOrg\Requests\Requests;
use WpOrg\Requests\Utility\InputValidator;
/** /**
* Session handler for persistent requests and default parameters * Session handler for persistent requests and default parameters
* *
@ -21,9 +14,10 @@ use WpOrg\Requests\Utility\InputValidator;
* with all subrequests resolved from this. Base options can be set (including * with all subrequests resolved from this. Base options can be set (including
* a shared cookie jar), then overridden for individual requests. * a shared cookie jar), then overridden for individual requests.
* *
* @package Requests\SessionHandler * @package Requests
* @subpackage Session Handler
*/ */
class Session { class Requests_Session {
/** /**
* Base URL for requests * Base URL for requests
* *
@ -38,7 +32,7 @@ class Session {
* *
* @var array * @var array
*/ */
public $headers = []; public $headers = array();
/** /**
* Base data for requests * Base data for requests
@ -48,7 +42,7 @@ class Session {
* *
* @var array * @var array
*/ */
public $data = []; public $data = array();
/** /**
* Base options for requests * Base options for requests
@ -61,57 +55,36 @@ class Session {
* *
* @var array * @var array
*/ */
public $options = []; public $options = array();
/** /**
* Create a new session * Create a new session
* *
* @param string|Stringable|null $url Base URL for requests * @param string|null $url Base URL for requests
* @param array $headers Default headers for requests * @param array $headers Default headers for requests
* @param array $data Default data for requests * @param array $data Default data for requests
* @param array $options Default options for requests * @param array $options Default options for requests
*
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string, Stringable or null.
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $headers argument is not an array.
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $data argument is not an array.
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
*/ */
public function __construct($url = null, $headers = [], $data = [], $options = []) { public function __construct($url = null, $headers = array(), $data = array(), $options = array()) {
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->url = $url;
$this->headers = $headers; $this->headers = $headers;
$this->data = $data; $this->data = $data;
$this->options = $options; $this->options = $options;
if (empty($this->options['cookies'])) { if (empty($this->options['cookies'])) {
$this->options['cookies'] = new Jar(); $this->options['cookies'] = new Requests_Cookie_Jar();
} }
} }
/** /**
* Get a property's value * Get a property's value
* *
* @param string $name Property name. * @param string $key Property key
* @return mixed|null Property value, null if none found * @return mixed|null Property value, null if none found
*/ */
public function __get($name) { public function __get($key) {
if (isset($this->options[$name])) { if (isset($this->options[$key])) {
return $this->options[$name]; return $this->options[$key];
} }
return null; return null;
@ -120,91 +93,93 @@ class Session {
/** /**
* Set a property's value * Set a property's value
* *
* @param string $name Property name. * @param string $key Property key
* @param mixed $value Property value * @param mixed $value Property value
*/ */
public function __set($name, $value) { public function __set($key, $value) {
$this->options[$name] = $value; $this->options[$key] = $value;
} }
/** /**
* Remove a property's value * Remove a property's value
* *
* @param string $name Property name. * @param string $key Property key
*/ */
public function __isset($name) { public function __isset($key) {
return isset($this->options[$name]); return isset($this->options[$key]);
} }
/** /**
* Remove a property's value * Remove a property's value
* *
* @param string $name Property name. * @param string $key Property key
*/ */
public function __unset($name) { public function __unset($key) {
unset($this->options[$name]); if (isset($this->options[$key])) {
unset($this->options[$key]);
}
} }
/**#@+ /**#@+
* @see \WpOrg\Requests\Session::request() * @see request()
* @param string $url * @param string $url
* @param array $headers * @param array $headers
* @param array $options * @param array $options
* @return \WpOrg\Requests\Response * @return Requests_Response
*/ */
/** /**
* Send a GET request * Send a GET request
*/ */
public function get($url, $headers = [], $options = []) { public function get($url, $headers = array(), $options = array()) {
return $this->request($url, $headers, null, Requests::GET, $options); return $this->request($url, $headers, null, Requests::GET, $options);
} }
/** /**
* Send a HEAD request * Send a HEAD request
*/ */
public function head($url, $headers = [], $options = []) { public function head($url, $headers = array(), $options = array()) {
return $this->request($url, $headers, null, Requests::HEAD, $options); return $this->request($url, $headers, null, Requests::HEAD, $options);
} }
/** /**
* Send a DELETE request * Send a DELETE request
*/ */
public function delete($url, $headers = [], $options = []) { public function delete($url, $headers = array(), $options = array()) {
return $this->request($url, $headers, null, Requests::DELETE, $options); return $this->request($url, $headers, null, Requests::DELETE, $options);
} }
/**#@-*/ /**#@-*/
/**#@+ /**#@+
* @see \WpOrg\Requests\Session::request() * @see request()
* @param string $url * @param string $url
* @param array $headers * @param array $headers
* @param array $data * @param array $data
* @param array $options * @param array $options
* @return \WpOrg\Requests\Response * @return Requests_Response
*/ */
/** /**
* Send a POST request * Send a POST request
*/ */
public function post($url, $headers = [], $data = [], $options = []) { public function post($url, $headers = array(), $data = array(), $options = array()) {
return $this->request($url, $headers, $data, Requests::POST, $options); return $this->request($url, $headers, $data, Requests::POST, $options);
} }
/** /**
* Send a PUT request * Send a PUT request
*/ */
public function put($url, $headers = [], $data = [], $options = []) { public function put($url, $headers = array(), $data = array(), $options = array()) {
return $this->request($url, $headers, $data, Requests::PUT, $options); return $this->request($url, $headers, $data, Requests::PUT, $options);
} }
/** /**
* Send a PATCH request * Send a PATCH request
* *
* Note: Unlike {@see \WpOrg\Requests\Session::post()} and {@see \WpOrg\Requests\Session::put()}, * Note: Unlike {@see post} and {@see put}, `$headers` is required, as the
* `$headers` is required, as the specification recommends that should send an ETag * specification recommends that should send an ETag
* *
* @link https://tools.ietf.org/html/rfc5789 * @link https://tools.ietf.org/html/rfc5789
*/ */
public function patch($url, $headers, $data = [], $options = []) { public function patch($url, $headers, $data = array(), $options = array()) {
return $this->request($url, $headers, $data, Requests::PATCH, $options); return $this->request($url, $headers, $data, Requests::PATCH, $options);
} }
/**#@-*/ /**#@-*/
@ -215,18 +190,18 @@ class Session {
* This method initiates a request and sends it via a transport before * This method initiates a request and sends it via a transport before
* parsing. * parsing.
* *
* @see \WpOrg\Requests\Requests::request() * @see Requests::request()
*
* @throws Requests_Exception On invalid URLs (`nonhttp`)
* *
* @param string $url URL to request * @param string $url URL to request
* @param array $headers Extra headers to send with the 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 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 string $type HTTP request type (use Requests constants)
* @param array $options Options for the request (see {@see \WpOrg\Requests\Requests::request()}) * @param array $options Options for the request (see {@see Requests::request})
* @return \WpOrg\Requests\Response * @return Requests_Response
*
* @throws \WpOrg\Requests\Exception On invalid URLs (`nonhttp`)
*/ */
public function request($url, $headers = [], $data = [], $type = Requests::GET, $options = []) { public function request($url, $headers = array(), $data = array(), $type = Requests::GET, $options = array()) {
$request = $this->merge_request(compact('url', 'headers', 'data', 'options')); $request = $this->merge_request(compact('url', 'headers', 'data', 'options'));
return Requests::request($request['url'], $request['headers'], $request['data'], $type, $request['options']); return Requests::request($request['url'], $request['headers'], $request['data'], $type, $request['options']);
@ -235,24 +210,13 @@ class Session {
/** /**
* Send multiple HTTP requests simultaneously * Send multiple HTTP requests simultaneously
* *
* @see \WpOrg\Requests\Requests::request_multiple() * @see Requests::request_multiple()
* *
* @param array $requests Requests data (see {@see \WpOrg\Requests\Requests::request_multiple()}) * @param array $requests Requests data (see {@see Requests::request_multiple})
* @param array $options Global and default options (see {@see \WpOrg\Requests\Requests::request()}) * @param array $options Global and default options (see {@see Requests::request})
* @return array Responses (either \WpOrg\Requests\Response or a \WpOrg\Requests\Exception object) * @return array Responses (either Requests_Response or a Requests_Exception object)
*
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access.
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
*/ */
public function request_multiple($requests, $options = []) { public function request_multiple($requests, $options = array()) {
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) { foreach ($requests as $key => $request) {
$requests[$key] = $this->merge_request($request, false); $requests[$key] = $this->merge_request($request, false);
} }
@ -268,18 +232,18 @@ class Session {
/** /**
* Merge a request's data with the default data * Merge a request's data with the default data
* *
* @param array $request Request data (same form as {@see \WpOrg\Requests\Session::request_multiple()}) * @param array $request Request data (same form as {@see request_multiple})
* @param boolean $merge_options Should we merge options as well? * @param boolean $merge_options Should we merge options as well?
* @return array Request data * @return array Request data
*/ */
protected function merge_request($request, $merge_options = true) { protected function merge_request($request, $merge_options = true) {
if ($this->url !== null) { if ($this->url !== null) {
$request['url'] = Iri::absolutize($this->url, $request['url']); $request['url'] = Requests_IRI::absolutize($this->url, $request['url']);
$request['url'] = $request['url']->uri; $request['url'] = $request['url']->uri;
} }
if (empty($request['headers'])) { if (empty($request['headers'])) {
$request['headers'] = []; $request['headers'] = array();
} }
$request['headers'] = array_merge($this->headers, $request['headers']); $request['headers'] = array_merge($this->headers, $request['headers']);
@ -292,7 +256,7 @@ class Session {
$request['data'] = array_merge($this->data, $request['data']); $request['data'] = array_merge($this->data, $request['data']);
} }
if ($merge_options === true) { if ($merge_options !== false) {
$request['options'] = array_merge($this->options, $request['options']); $request['options'] = array_merge($this->options, $request['options']);
// Disallow forcing the type, as that's a per request setting // Disallow forcing the type, as that's a per request setting

View File

@ -2,44 +2,40 @@
/** /**
* Base HTTP transport * Base HTTP transport
* *
* @package Requests\Transport * @package Requests
* @subpackage Transport
*/ */
namespace WpOrg\Requests;
/** /**
* Base HTTP transport * Base HTTP transport
* *
* @package Requests\Transport * @package Requests
* @subpackage Transport
*/ */
interface Transport { interface Requests_Transport {
/** /**
* Perform a request * Perform a request
* *
* @param string $url URL to request * @param string $url URL to request
* @param array $headers Associative array of request headers * @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 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 * @param array $options Request options, see {@see Requests::response()} for documentation
* @return string Raw HTTP result * @return string Raw HTTP result
*/ */
public function request($url, $headers = [], $data = [], $options = []); public function request($url, $headers = array(), $data = array(), $options = array());
/** /**
* Send multiple requests simultaneously * Send multiple requests simultaneously
* *
* @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see \WpOrg\Requests\Transport::request()} * @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see Requests_Transport::request}
* @param array $options Global options, see {@see \WpOrg\Requests\Requests::response()} for documentation * @param array $options Global options, see {@see Requests::response()} for documentation
* @return array Array of \WpOrg\Requests\Response objects (may contain \WpOrg\Requests\Exception or string responses as well) * @return array Array of Requests_Response objects (may contain Requests_Exception or string responses as well)
*/ */
public function request_multiple($requests, $options); public function request_multiple($requests, $options);
/** /**
* Self-test whether the transport can be used. * Self-test whether the transport can be used
* * @return bool
* 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 = []); public static function test();
} }

View File

@ -2,27 +2,17 @@
/** /**
* cURL HTTP transport * cURL HTTP transport
* *
* @package Requests\Transport * @package Requests
* @subpackage Transport
*/ */
namespace WpOrg\Requests\Transport;
use RecursiveArrayIterator;
use RecursiveIteratorIterator;
use WpOrg\Requests\Capability;
use WpOrg\Requests\Exception;
use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\Exception\Transport\Curl as CurlException;
use WpOrg\Requests\Requests;
use WpOrg\Requests\Transport;
use WpOrg\Requests\Utility\InputValidator;
/** /**
* cURL HTTP transport * cURL HTTP transport
* *
* @package Requests\Transport * @package Requests
* @subpackage Transport
*/ */
final class Curl implements Transport { class Requests_Transport_cURL implements Requests_Transport {
const CURL_7_10_5 = 0x070A05; const CURL_7_10_5 = 0x070A05;
const CURL_7_16_2 = 0x071002; const CURL_7_16_2 = 0x071002;
@ -43,7 +33,7 @@ final class Curl implements Transport {
/** /**
* Information on the current request * Information on the current request
* *
* @var array cURL information array, see {@link https://www.php.net/curl_getinfo} * @var array cURL information array, see {@see https://secure.php.net/curl_getinfo}
*/ */
public $info; public $info;
@ -57,44 +47,44 @@ final class Curl implements Transport {
/** /**
* cURL handle * cURL handle
* *
* @var resource|\CurlHandle Resource in PHP < 8.0, Instance of CurlHandle in PHP >= 8.0. * @var resource
*/ */
private $handle; protected $handle;
/** /**
* Hook dispatcher instance * Hook dispatcher instance
* *
* @var \WpOrg\Requests\Hooks * @var Requests_Hooks
*/ */
private $hooks; protected $hooks;
/** /**
* Have we finished the headers yet? * Have we finished the headers yet?
* *
* @var boolean * @var boolean
*/ */
private $done_headers = false; protected $done_headers = false;
/** /**
* If streaming to a file, keep the file pointer * If streaming to a file, keep the file pointer
* *
* @var resource * @var resource
*/ */
private $stream_handle; protected $stream_handle;
/** /**
* How many bytes are in the response body? * How many bytes are in the response body?
* *
* @var int * @var int
*/ */
private $response_bytes; protected $response_bytes;
/** /**
* What's the maximum number of bytes we should keep? * What's the maximum number of bytes we should keep?
* *
* @var int|bool Byte count, or false if no limit. * @var int|bool Byte count, or false if no limit.
*/ */
private $response_byte_limit; protected $response_byte_limit;
/** /**
* Constructor * Constructor
@ -131,52 +121,23 @@ final class Curl implements Transport {
/** /**
* Perform a request * Perform a request
* *
* @param string|Stringable $url URL to request * @throws Requests_Exception On a cURL error (`curlerror`)
*
* @param string $url URL to request
* @param array $headers Associative array of request headers * @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 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 * @param array $options Request options, see {@see Requests::response()} for documentation
* @return string Raw HTTP result * @return string Raw HTTP result
*
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string or Stringable.
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $headers argument is not an array.
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $data parameter is not an array or string.
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
* @throws \WpOrg\Requests\Exception On a cURL error (`curlerror`)
*/ */
public function request($url, $headers = [], $data = [], $options = []) { public function request($url, $headers = array(), $data = array(), $options = array()) {
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->hooks = $options['hooks'];
$this->setup_handle($url, $headers, $data, $options); $this->setup_handle($url, $headers, $data, $options);
$options['hooks']->dispatch('curl.before_send', [&$this->handle]); $options['hooks']->dispatch('curl.before_send', array(&$this->handle));
if ($options['filename'] !== false) { 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');
$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_data = '';
@ -203,9 +164,9 @@ final class Curl implements Transport {
curl_exec($this->handle); curl_exec($this->handle);
$response = $this->response_data; $response = $this->response_data;
$options['hooks']->dispatch('curl.after_send', []); $options['hooks']->dispatch('curl.after_send', array());
if (curl_errno($this->handle) === CURLE_WRITE_ERROR || curl_errno($this->handle) === CURLE_BAD_CONTENT_ENCODING) { if (curl_errno($this->handle) === 23 || curl_errno($this->handle) === 61) {
// Reset encoding and try again // Reset encoding and try again
curl_setopt($this->handle, CURLOPT_ENCODING, 'none'); curl_setopt($this->handle, CURLOPT_ENCODING, 'none');
@ -218,7 +179,7 @@ final class Curl implements Transport {
$this->process_response($response, $options); $this->process_response($response, $options);
// Need to remove the $this reference from the curl handle. // 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. // Otherwise Requests_Transport_cURL wont be garbage collected and the curl_close() will never be called.
curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, null); curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, null);
curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, null); curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, null);
@ -230,42 +191,31 @@ final class Curl implements Transport {
* *
* @param array $requests Request data * @param array $requests Request data
* @param array $options Global options * @param array $options Global options
* @return array Array of \WpOrg\Requests\Response objects (may contain \WpOrg\Requests\Exception or string responses as well) * @return array Array of Requests_Response objects (may contain Requests_Exception or string responses as well)
*
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access.
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
*/ */
public function request_multiple($requests, $options) { public function request_multiple($requests, $options) {
// If you're not requesting, we can't get any responses ¯\_(ツ)_/¯ // If you're not requesting, we can't get any responses ¯\_(ツ)_/¯
if (empty($requests)) { if (empty($requests)) {
return []; return array();
}
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(); $multihandle = curl_multi_init();
$subrequests = []; $subrequests = array();
$subhandles = []; $subhandles = array();
$class = get_class($this); $class = get_class($this);
foreach ($requests as $id => $request) { foreach ($requests as $id => $request) {
$subrequests[$id] = new $class(); $subrequests[$id] = new $class();
$subhandles[$id] = $subrequests[$id]->get_subrequest_handle($request['url'], $request['headers'], $request['data'], $request['options']); $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]]); $request['options']['hooks']->dispatch('curl.before_multi_add', array(&$subhandles[$id]));
curl_multi_add_handle($multihandle, $subhandles[$id]); curl_multi_add_handle($multihandle, $subhandles[$id]);
} }
$completed = 0; $completed = 0;
$responses = []; $responses = array();
$subrequestcount = count($subrequests); $subrequestcount = count($subrequests);
$request['options']['hooks']->dispatch('curl.before_multi_exec', [&$multihandle]); $request['options']['hooks']->dispatch('curl.before_multi_exec', array(&$multihandle));
do { do {
$active = 0; $active = 0;
@ -275,7 +225,7 @@ final class Curl implements Transport {
} }
while ($status === CURLM_CALL_MULTI_PERFORM); while ($status === CURLM_CALL_MULTI_PERFORM);
$to_process = []; $to_process = array();
// Read the information as needed // Read the information as needed
while ($done = curl_multi_info_read($multihandle)) { while ($done = curl_multi_info_read($multihandle)) {
@ -291,33 +241,33 @@ final class Curl implements Transport {
if ($done['result'] !== CURLE_OK) { if ($done['result'] !== CURLE_OK) {
//get error string for handle. //get error string for handle.
$reason = curl_error($done['handle']); $reason = curl_error($done['handle']);
$exception = new CurlException( $exception = new Requests_Exception_Transport_cURL(
$reason, $reason,
CurlException::EASY, Requests_Exception_Transport_cURL::EASY,
$done['handle'], $done['handle'],
$done['result'] $done['result']
); );
$responses[$key] = $exception; $responses[$key] = $exception;
$options['hooks']->dispatch('transport.internal.parse_error', [&$responses[$key], $requests[$key]]); $options['hooks']->dispatch('transport.internal.parse_error', array(&$responses[$key], $requests[$key]));
} }
else { else {
$responses[$key] = $subrequests[$key]->process_response($subrequests[$key]->response_data, $options); $responses[$key] = $subrequests[$key]->process_response($subrequests[$key]->response_data, $options);
$options['hooks']->dispatch('transport.internal.parse_response', [&$responses[$key], $requests[$key]]); $options['hooks']->dispatch('transport.internal.parse_response', array(&$responses[$key], $requests[$key]));
} }
curl_multi_remove_handle($multihandle, $done['handle']); curl_multi_remove_handle($multihandle, $done['handle']);
curl_close($done['handle']); curl_close($done['handle']);
if (!is_string($responses[$key])) { if (!is_string($responses[$key])) {
$options['hooks']->dispatch('multiple.request.complete', [&$responses[$key], $key]); $options['hooks']->dispatch('multiple.request.complete', array(&$responses[$key], $key));
} }
$completed++; $completed++;
} }
} }
while ($active || $completed < $subrequestcount); while ($active || $completed < $subrequestcount);
$request['options']['hooks']->dispatch('curl.after_multi_exec', [&$multihandle]); $request['options']['hooks']->dispatch('curl.after_multi_exec', array(&$multihandle));
curl_multi_close($multihandle); curl_multi_close($multihandle);
@ -330,8 +280,8 @@ final class Curl implements Transport {
* @param string $url URL to request * @param string $url URL to request
* @param array $headers Associative array of request headers * @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 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 * @param array $options Request options, see {@see Requests::response()} for documentation
* @return resource|\CurlHandle Subrequest's cURL handle * @return resource Subrequest's cURL handle
*/ */
public function &get_subrequest_handle($url, $headers, $data, $options) { public function &get_subrequest_handle($url, $headers, $data, $options) {
$this->setup_handle($url, $headers, $data, $options); $this->setup_handle($url, $headers, $data, $options);
@ -357,10 +307,10 @@ final class Curl implements Transport {
* @param string $url URL to request * @param string $url URL to request
* @param array $headers Associative array of request headers * @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 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 * @param array $options Request options, see {@see Requests::response()} for documentation
*/ */
private function setup_handle($url, $headers, $data, $options) { protected function setup_handle($url, $headers, $data, $options) {
$options['hooks']->dispatch('curl.before_request', [&$this->handle]); $options['hooks']->dispatch('curl.before_request', array(&$this->handle));
// Force closing the connection for old versions of cURL (<7.22). // Force closing the connection for old versions of cURL (<7.22).
if (!isset($headers['Connection'])) { if (!isset($headers['Connection'])) {
@ -392,7 +342,7 @@ final class Curl implements Transport {
$data = ''; $data = '';
} }
elseif (!is_string($data)) { elseif (!is_string($data)) {
$data = http_build_query($data, '', '&'); $data = http_build_query($data, null, '&');
} }
} }
@ -443,6 +393,7 @@ final class Curl implements Transport {
curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT_MS, round($options['connect_timeout'] * 1000)); curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT_MS, round($options['connect_timeout'] * 1000));
} }
curl_setopt($this->handle, CURLOPT_URL, $url); curl_setopt($this->handle, CURLOPT_URL, $url);
curl_setopt($this->handle, CURLOPT_REFERER, $url);
curl_setopt($this->handle, CURLOPT_USERAGENT, $options['useragent']); curl_setopt($this->handle, CURLOPT_USERAGENT, $options['useragent']);
if (!empty($headers)) { if (!empty($headers)) {
curl_setopt($this->handle, CURLOPT_HTTPHEADER, $headers); curl_setopt($this->handle, CURLOPT_HTTPHEADER, $headers);
@ -455,8 +406,8 @@ final class Curl implements Transport {
} }
if ($options['blocking'] === true) { if ($options['blocking'] === true) {
curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, [$this, 'stream_headers']); curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, array($this, 'stream_headers'));
curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, [$this, 'stream_body']); curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, array($this, 'stream_body'));
curl_setopt($this->handle, CURLOPT_BUFFERSIZE, Requests::BUFFER_SIZE); curl_setopt($this->handle, CURLOPT_BUFFERSIZE, Requests::BUFFER_SIZE);
} }
} }
@ -467,12 +418,12 @@ final class Curl implements Transport {
* @param string $response Response data from the body * @param string $response Response data from the body
* @param array $options Request options * @param array $options Request options
* @return string|false HTTP response data including headers. False if non-blocking. * @return string|false HTTP response data including headers. False if non-blocking.
* @throws \WpOrg\Requests\Exception * @throws Requests_Exception
*/ */
public function process_response($response, $options) { public function process_response($response, $options) {
if ($options['blocking'] === false) { if ($options['blocking'] === false) {
$fake_headers = ''; $fake_headers = '';
$options['hooks']->dispatch('curl.after_request', [&$fake_headers]); $options['hooks']->dispatch('curl.after_request', array(&$fake_headers));
return false; return false;
} }
if ($options['filename'] !== false && $this->stream_handle) { if ($options['filename'] !== false && $this->stream_handle) {
@ -489,18 +440,18 @@ final class Curl implements Transport {
curl_errno($this->handle), curl_errno($this->handle),
curl_error($this->handle) curl_error($this->handle)
); );
throw new Exception($error, 'curlerror', $this->handle); throw new Requests_Exception($error, 'curlerror', $this->handle);
} }
$this->info = curl_getinfo($this->handle); $this->info = curl_getinfo($this->handle);
$options['hooks']->dispatch('curl.after_request', [&$this->headers, &$this->info]); $options['hooks']->dispatch('curl.after_request', array(&$this->headers, &$this->info));
return $this->headers; return $this->headers;
} }
/** /**
* Collect the headers as they are received * Collect the headers as they are received
* *
* @param resource|\CurlHandle $handle cURL handle * @param resource $handle cURL resource
* @param string $headers Header string * @param string $headers Header string
* @return integer Length of provided header * @return integer Length of provided header
*/ */
@ -525,12 +476,12 @@ final class Curl implements Transport {
* *
* @since 1.6.1 * @since 1.6.1
* *
* @param resource|\CurlHandle $handle cURL handle * @param resource $handle cURL resource
* @param string $data Body data * @param string $data Body data
* @return integer Length of provided data * @return integer Length of provided data
*/ */
public function stream_body($handle, $data) { public function stream_body($handle, $data) {
$this->hooks->dispatch('request.progress', [$data, $this->response_bytes, $this->response_byte_limit]); $this->hooks->dispatch('request.progress', array($data, $this->response_bytes, $this->response_byte_limit));
$data_length = strlen($data); $data_length = strlen($data);
// Are we limiting the response size? // Are we limiting the response size?
@ -562,10 +513,10 @@ final class Curl implements Transport {
* Format a URL given GET data * Format a URL given GET data
* *
* @param string $url * @param string $url
* @param array|object $data Data to build query using, see {@link https://www.php.net/http_build_query} * @param array|object $data Data to build query using, see {@see https://secure.php.net/http_build_query}
* @return string URL with data * @return string URL with data
*/ */
private static function format_get($url, $data) { protected static function format_get($url, $data) {
if (!empty($data)) { if (!empty($data)) {
$query = ''; $query = '';
$url_parts = parse_url($url); $url_parts = parse_url($url);
@ -576,7 +527,7 @@ final class Curl implements Transport {
$query = $url_parts['query']; $query = $url_parts['query'];
} }
$query .= '&' . http_build_query($data, '', '&'); $query .= '&' . http_build_query($data, null, '&');
$query = trim($query, '&'); $query = trim($query, '&');
if (empty($url_parts['query'])) { if (empty($url_parts['query'])) {
@ -590,21 +541,18 @@ final class Curl implements Transport {
} }
/** /**
* Self-test whether the transport can be used. * Whether this transport is valid
*
* The available capabilities to test for can be found in {@see \WpOrg\Requests\Capability}.
* *
* @codeCoverageIgnore * @codeCoverageIgnore
* @param array<string, bool> $capabilities Optional. Associative array of capabilities to test against, i.e. `['<capability>' => true]`. * @return boolean True if the transport is valid, false otherwise.
* @return bool Whether the transport can be used.
*/ */
public static function test($capabilities = []) { public static function test($capabilities = array()) {
if (!function_exists('curl_init') || !function_exists('curl_exec')) { if (!function_exists('curl_init') || !function_exists('curl_exec')) {
return false; return false;
} }
// If needed, check that our installed curl version supports SSL // If needed, check that our installed curl version supports SSL
if (isset($capabilities[Capability::SSL]) && $capabilities[Capability::SSL]) { if (isset($capabilities['ssl']) && $capabilities['ssl']) {
$curl_version = curl_version(); $curl_version = curl_version();
if (!(CURL_VERSION_SSL & $curl_version['features'])) { if (!(CURL_VERSION_SSL & $curl_version['features'])) {
return false; return false;
@ -620,7 +568,7 @@ final class Curl implements Transport {
* @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD. * @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. * @return string The "Expect" header.
*/ */
private function get_expect_header($data) { protected function get_expect_header($data) {
if (!is_array($data)) { if (!is_array($data)) {
return strlen((string) $data) >= 1048576 ? '100-Continue' : ''; return strlen((string) $data) >= 1048576 ? '100-Continue' : '';
} }

View File

@ -2,27 +2,17 @@
/** /**
* fsockopen HTTP transport * fsockopen HTTP transport
* *
* @package Requests\Transport * @package Requests
* @subpackage Transport
*/ */
namespace WpOrg\Requests\Transport;
use WpOrg\Requests\Capability;
use WpOrg\Requests\Exception;
use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\Port;
use WpOrg\Requests\Requests;
use WpOrg\Requests\Ssl;
use WpOrg\Requests\Transport;
use WpOrg\Requests\Utility\CaseInsensitiveDictionary;
use WpOrg\Requests\Utility\InputValidator;
/** /**
* fsockopen HTTP transport * fsockopen HTTP transport
* *
* @package Requests\Transport * @package Requests
* @subpackage Transport
*/ */
final class Fsockopen implements Transport { class Requests_Transport_fsockopen implements Requests_Transport {
/** /**
* Second to microsecond conversion * Second to microsecond conversion
* *
@ -40,7 +30,7 @@ final class Fsockopen implements Transport {
/** /**
* Stream metadata * Stream metadata
* *
* @var array Associative array of properties, see {@link https://www.php.net/stream_get_meta_data} * @var array Associative array of properties, see {@see https://secure.php.net/stream_get_meta_data}
*/ */
public $info; public $info;
@ -49,69 +39,45 @@ final class Fsockopen implements Transport {
* *
* @var int|bool Byte count, or false if no limit. * @var int|bool Byte count, or false if no limit.
*/ */
private $max_bytes = false; protected $max_bytes = false;
private $connect_error = ''; protected $connect_error = '';
/** /**
* Perform a request * Perform a request
* *
* @param string|Stringable $url URL to request * @throws Requests_Exception On failure to connect to socket (`fsockopenerror`)
* @throws Requests_Exception On socket timeout (`timeout`)
*
* @param string $url URL to request
* @param array $headers Associative array of request headers * @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 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 * @param array $options Request options, see {@see Requests::response()} for documentation
* @return string Raw HTTP result * @return string Raw HTTP result
*
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string or Stringable.
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $headers argument is not an array.
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $data parameter is not an array or string.
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
* @throws \WpOrg\Requests\Exception On failure to connect to socket (`fsockopenerror`)
* @throws \WpOrg\Requests\Exception On socket timeout (`timeout`)
*/ */
public function request($url, $headers = [], $data = [], $options = []) { public function request($url, $headers = array(), $data = array(), $options = array()) {
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'); $options['hooks']->dispatch('fsockopen.before_request');
$url_parts = parse_url($url); $url_parts = parse_url($url);
if (empty($url_parts)) { if (empty($url_parts)) {
throw new Exception('Invalid URL.', 'invalidurl', $url); throw new Requests_Exception('Invalid URL.', 'invalidurl', $url);
} }
$host = $url_parts['host']; $host = $url_parts['host'];
$context = stream_context_create(); $context = stream_context_create();
$verifyname = false; $verifyname = false;
$case_insensitive_headers = new CaseInsensitiveDictionary($headers); $case_insensitive_headers = new Requests_Utility_CaseInsensitiveDictionary($headers);
// HTTPS support // HTTPS support
if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') { if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') {
$remote_socket = 'ssl://' . $host; $remote_socket = 'ssl://' . $host;
if (!isset($url_parts['port'])) { if (!isset($url_parts['port'])) {
$url_parts['port'] = Port::HTTPS; $url_parts['port'] = 443;
} }
$context_options = [ $context_options = array(
'verify_peer' => true, 'verify_peer' => true,
'capture_peer_cert' => true, 'capture_peer_cert' => true,
]; );
$verifyname = true; $verifyname = true;
// SNI, if enabled (OpenSSL >=0.9.8j) // SNI, if enabled (OpenSSL >=0.9.8j)
@ -139,7 +105,7 @@ final class Fsockopen implements Transport {
$verifyname = false; $verifyname = false;
} }
stream_context_set_option($context, ['ssl' => $context_options]); stream_context_set_option($context, array('ssl' => $context_options));
} }
else { else {
$remote_socket = 'tcp://' . $host; $remote_socket = 'tcp://' . $host;
@ -148,30 +114,30 @@ final class Fsockopen implements Transport {
$this->max_bytes = $options['max_bytes']; $this->max_bytes = $options['max_bytes'];
if (!isset($url_parts['port'])) { if (!isset($url_parts['port'])) {
$url_parts['port'] = Port::HTTP; $url_parts['port'] = 80;
} }
$remote_socket .= ':' . $url_parts['port']; $remote_socket .= ':' . $url_parts['port'];
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler
set_error_handler([$this, 'connect_error_handler'], E_WARNING | E_NOTICE); set_error_handler(array($this, 'connect_error_handler'), E_WARNING | E_NOTICE);
$options['hooks']->dispatch('fsockopen.remote_socket', [&$remote_socket]); $options['hooks']->dispatch('fsockopen.remote_socket', array(&$remote_socket));
$socket = stream_socket_client($remote_socket, $errno, $errstr, ceil($options['connect_timeout']), STREAM_CLIENT_CONNECT, $context); $socket = stream_socket_client($remote_socket, $errno, $errstr, ceil($options['connect_timeout']), STREAM_CLIENT_CONNECT, $context);
restore_error_handler(); restore_error_handler();
if ($verifyname && !$this->verify_certificate_from_context($host, $context)) { if ($verifyname && !$this->verify_certificate_from_context($host, $context)) {
throw new Exception('SSL certificate did not match the requested domain name', 'ssl.no_match'); throw new Requests_Exception('SSL certificate did not match the requested domain name', 'ssl.no_match');
} }
if (!$socket) { if (!$socket) {
if ($errno === 0) { if ($errno === 0) {
// Connection issue // Connection issue
throw new Exception(rtrim($this->connect_error), 'fsockopen.connect_error'); throw new Requests_Exception(rtrim($this->connect_error), 'fsockopen.connect_error');
} }
throw new Exception($errstr, 'fsockopenerror', null, $errno); throw new Requests_Exception($errstr, 'fsockopenerror', null, $errno);
} }
$data_format = $options['data_format']; $data_format = $options['data_format'];
@ -181,10 +147,10 @@ final class Fsockopen implements Transport {
$data = ''; $data = '';
} }
else { else {
$path = self::format_get($url_parts, []); $path = self::format_get($url_parts, array());
} }
$options['hooks']->dispatch('fsockopen.remote_host_path', [&$path, $url]); $options['hooks']->dispatch('fsockopen.remote_host_path', array(&$path, $url));
$request_body = ''; $request_body = '';
$out = sprintf("%s %s HTTP/%.1F\r\n", $options['type'], $path, $options['protocol_version']); $out = sprintf("%s %s HTTP/%.1F\r\n", $options['type'], $path, $options['protocol_version']);
@ -212,9 +178,8 @@ final class Fsockopen implements Transport {
if (!isset($case_insensitive_headers['Host'])) { if (!isset($case_insensitive_headers['Host'])) {
$out .= sprintf('Host: %s', $url_parts['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)) { if ((strtolower($url_parts['scheme']) === 'http' && $url_parts['port'] !== 80) || (strtolower($url_parts['scheme']) === 'https' && $url_parts['port'] !== 443)) {
$out .= ':' . $url_parts['port']; $out .= ':' . $url_parts['port'];
} }
$out .= "\r\n"; $out .= "\r\n";
@ -235,7 +200,7 @@ final class Fsockopen implements Transport {
$out .= implode("\r\n", $headers) . "\r\n"; $out .= implode("\r\n", $headers) . "\r\n";
} }
$options['hooks']->dispatch('fsockopen.after_headers', [&$out]); $options['hooks']->dispatch('fsockopen.after_headers', array(&$out));
if (substr($out, -2) !== "\r\n") { if (substr($out, -2) !== "\r\n") {
$out .= "\r\n"; $out .= "\r\n";
@ -247,15 +212,15 @@ final class Fsockopen implements Transport {
$out .= "\r\n" . $request_body; $out .= "\r\n" . $request_body;
$options['hooks']->dispatch('fsockopen.before_send', [&$out]); $options['hooks']->dispatch('fsockopen.before_send', array(&$out));
fwrite($socket, $out); fwrite($socket, $out);
$options['hooks']->dispatch('fsockopen.after_send', [$out]); $options['hooks']->dispatch('fsockopen.after_send', array($out));
if (!$options['blocking']) { if (!$options['blocking']) {
fclose($socket); fclose($socket);
$fake_headers = ''; $fake_headers = '';
$options['hooks']->dispatch('fsockopen.after_request', [&$fake_headers]); $options['hooks']->dispatch('fsockopen.after_request', array(&$fake_headers));
return ''; return '';
} }
@ -276,18 +241,13 @@ final class Fsockopen implements Transport {
$doingbody = false; $doingbody = false;
$download = false; $download = false;
if ($options['filename']) { if ($options['filename']) {
// phpcs:ignore WordPress.PHP.NoSilencedErrors -- Silenced the PHP native warning in favour of throwing an exception. $download = fopen($options['filename'], 'wb');
$download = @fopen($options['filename'], 'wb');
if ($download === false) {
$error = error_get_last();
throw new Exception($error['message'], 'fopen');
}
} }
while (!feof($socket)) { while (!feof($socket)) {
$this->info = stream_get_meta_data($socket); $this->info = stream_get_meta_data($socket);
if ($this->info['timed_out']) { if ($this->info['timed_out']) {
throw new Exception('fsocket timed out', 'timeout'); throw new Requests_Exception('fsocket timed out', 'timeout');
} }
$block = fread($socket, Requests::BUFFER_SIZE); $block = fread($socket, Requests::BUFFER_SIZE);
@ -301,7 +261,7 @@ final class Fsockopen implements Transport {
// Are we in body mode now? // Are we in body mode now?
if ($doingbody) { if ($doingbody) {
$options['hooks']->dispatch('request.progress', [$block, $size, $this->max_bytes]); $options['hooks']->dispatch('request.progress', array($block, $size, $this->max_bytes));
$data_length = strlen($block); $data_length = strlen($block);
if ($this->max_bytes) { if ($this->max_bytes) {
// Have we already hit a limit? // Have we already hit a limit?
@ -334,49 +294,33 @@ final class Fsockopen implements Transport {
} }
fclose($socket); fclose($socket);
$options['hooks']->dispatch('fsockopen.after_request', [&$this->headers, &$this->info]); $options['hooks']->dispatch('fsockopen.after_request', array(&$this->headers, &$this->info));
return $this->headers; return $this->headers;
} }
/** /**
* Send multiple requests simultaneously * Send multiple requests simultaneously
* *
* @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see \WpOrg\Requests\Transport::request()} * @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see Requests_Transport::request}
* @param array $options Global options, see {@see \WpOrg\Requests\Requests::response()} for documentation * @param array $options Global options, see {@see Requests::response()} for documentation
* @return array Array of \WpOrg\Requests\Response objects (may contain \WpOrg\Requests\Exception or string responses as well) * @return array Array of Requests_Response objects (may contain Requests_Exception or string responses as well)
*
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access.
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
*/ */
public function request_multiple($requests, $options) { public function request_multiple($requests, $options) {
// If you're not requesting, we can't get any responses ¯\_(ツ)_/¯ $responses = array();
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); $class = get_class($this);
foreach ($requests as $id => $request) { foreach ($requests as $id => $request) {
try { try {
$handler = new $class(); $handler = new $class();
$responses[$id] = $handler->request($request['url'], $request['headers'], $request['data'], $request['options']); $responses[$id] = $handler->request($request['url'], $request['headers'], $request['data'], $request['options']);
$request['options']['hooks']->dispatch('transport.internal.parse_response', [&$responses[$id], $request]); $request['options']['hooks']->dispatch('transport.internal.parse_response', array(&$responses[$id], $request));
} }
catch (Exception $e) { catch (Requests_Exception $e) {
$responses[$id] = $e; $responses[$id] = $e;
} }
if (!is_string($responses[$id])) { if (!is_string($responses[$id])) {
$request['options']['hooks']->dispatch('multiple.request.complete', [&$responses[$id], $id]); $request['options']['hooks']->dispatch('multiple.request.complete', array(&$responses[$id], $id));
} }
} }
@ -388,8 +332,8 @@ final class Fsockopen implements Transport {
* *
* @return string Accept-Encoding header value * @return string Accept-Encoding header value
*/ */
private static function accept_encoding() { protected static function accept_encoding() {
$type = []; $type = array();
if (function_exists('gzinflate')) { if (function_exists('gzinflate')) {
$type[] = 'deflate;q=1.0'; $type[] = 'deflate;q=1.0';
} }
@ -407,10 +351,10 @@ final class Fsockopen implements Transport {
* Format a URL given GET data * Format a URL given GET data
* *
* @param array $url_parts * @param array $url_parts
* @param array|object $data Data to build query using, see {@link https://www.php.net/http_build_query} * @param array|object $data Data to build query using, see {@see https://secure.php.net/http_build_query}
* @return string URL with data * @return string URL with data
*/ */
private static function format_get($url_parts, $data) { protected static function format_get($url_parts, $data) {
if (!empty($data)) { if (!empty($data)) {
if (empty($url_parts['query'])) { if (empty($url_parts['query'])) {
$url_parts['query'] = ''; $url_parts['query'] = '';
@ -457,14 +401,13 @@ final class Fsockopen implements Transport {
* names, leading things like 'https://www.github.com/' to be invalid. * names, leading things like 'https://www.github.com/' to be invalid.
* Instead * Instead
* *
* @link https://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1 * @see https://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1
* *
* @throws Requests_Exception On failure to connect via TLS (`fsockopen.ssl.connect_error`)
* @throws Requests_Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`)
* @param string $host Host name to verify against * @param string $host Host name to verify against
* @param resource $context Stream context * @param resource $context Stream context
* @return bool * @return bool
*
* @throws \WpOrg\Requests\Exception On failure to connect via TLS (`fsockopen.ssl.connect_error`)
* @throws \WpOrg\Requests\Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`)
*/ */
public function verify_certificate_from_context($host, $context) { public function verify_certificate_from_context($host, $context) {
$meta = stream_context_get_options($context); $meta = stream_context_get_options($context);
@ -472,33 +415,35 @@ final class Fsockopen implements Transport {
// If we don't have SSL options, then we couldn't make the connection at // If we don't have SSL options, then we couldn't make the connection at
// all // all
if (empty($meta) || empty($meta['ssl']) || empty($meta['ssl']['peer_certificate'])) { if (empty($meta) || empty($meta['ssl']) || empty($meta['ssl']['peer_certificate'])) {
throw new Exception(rtrim($this->connect_error), 'ssl.connect_error'); throw new Requests_Exception(rtrim($this->connect_error), 'ssl.connect_error');
} }
$cert = openssl_x509_parse($meta['ssl']['peer_certificate']); $cert = openssl_x509_parse($meta['ssl']['peer_certificate']);
return Ssl::verify_certificate($host, $cert); return Requests_SSL::verify_certificate($host, $cert);
} }
/** /**
* Self-test whether the transport can be used. * Whether this transport is valid
*
* The available capabilities to test for can be found in {@see \WpOrg\Requests\Capability}.
* *
* @codeCoverageIgnore * @codeCoverageIgnore
* @param array<string, bool> $capabilities Optional. Associative array of capabilities to test against, i.e. `['<capability>' => true]`. * @return boolean True if the transport is valid, false otherwise.
* @return bool Whether the transport can be used.
*/ */
public static function test($capabilities = []) { public static function test($capabilities = array()) {
if (!function_exists('fsockopen')) { if (!function_exists('fsockopen')) {
return false; return false;
} }
// If needed, check that streams support SSL // If needed, check that streams support SSL
if (isset($capabilities[Capability::SSL]) && $capabilities[Capability::SSL]) { if (isset($capabilities['ssl']) && $capabilities['ssl']) {
if (!extension_loaded('openssl') || !function_exists('openssl_x509_parse')) { if (!extension_loaded('openssl') || !function_exists('openssl_x509_parse')) {
return false; return false;
} }
// Currently broken, thanks to https://github.com/facebook/hhvm/issues/2156
if (defined('HHVM_VERSION')) {
return false;
}
} }
return true; return true;

View File

@ -2,116 +2,92 @@
/** /**
* Case-insensitive dictionary, suitable for HTTP headers * Case-insensitive dictionary, suitable for HTTP headers
* *
* @package Requests\Utilities * @package Requests
* @subpackage Utilities
*/ */
namespace WpOrg\Requests\Utility;
use ArrayAccess;
use ArrayIterator;
use IteratorAggregate;
use ReturnTypeWillChange;
use WpOrg\Requests\Exception;
/** /**
* Case-insensitive dictionary, suitable for HTTP headers * Case-insensitive dictionary, suitable for HTTP headers
* *
* @package Requests\Utilities * @package Requests
* @subpackage Utilities
*/ */
class CaseInsensitiveDictionary implements ArrayAccess, IteratorAggregate { class Requests_Utility_CaseInsensitiveDictionary implements ArrayAccess, IteratorAggregate {
/** /**
* Actual item data * Actual item data
* *
* @var array * @var array
*/ */
protected $data = []; protected $data = array();
/** /**
* Creates a case insensitive dictionary. * Creates a case insensitive dictionary.
* *
* @param array $data Dictionary/map to convert to case-insensitive * @param array $data Dictionary/map to convert to case-insensitive
*/ */
public function __construct(array $data = []) { public function __construct(array $data = array()) {
foreach ($data as $offset => $value) { foreach ($data as $key => $value) {
$this->offsetSet($offset, $value); $this->offsetSet($key, $value);
} }
} }
/** /**
* Check if the given item exists * Check if the given item exists
* *
* @param string $offset Item key * @param string $key Item key
* @return boolean Does the item exist? * @return boolean Does the item exist?
*/ */
#[ReturnTypeWillChange] public function offsetExists($key) {
public function offsetExists($offset) { $key = strtolower($key);
if (is_string($offset)) { return isset($this->data[$key]);
$offset = strtolower($offset);
}
return isset($this->data[$offset]);
} }
/** /**
* Get the value for the item * Get the value for the item
* *
* @param string $offset Item key * @param string $key Item key
* @return string|null Item value (null if the item key doesn't exist) * @return string|null Item value (null if offsetExists is false)
*/ */
#[ReturnTypeWillChange] public function offsetGet($key) {
public function offsetGet($offset) { $key = strtolower($key);
if (is_string($offset)) { if (!isset($this->data[$key])) {
$offset = strtolower($offset);
}
if (!isset($this->data[$offset])) {
return null; return null;
} }
return $this->data[$offset]; return $this->data[$key];
} }
/** /**
* Set the given item * Set the given item
* *
* @param string $offset Item name * @throws Requests_Exception On attempting to use dictionary as list (`invalidset`)
* @param string $value Item value
* *
* @throws \WpOrg\Requests\Exception On attempting to use dictionary as list (`invalidset`) * @param string $key Item name
* @param string $value Item value
*/ */
#[ReturnTypeWillChange] public function offsetSet($key, $value) {
public function offsetSet($offset, $value) { if ($key === null) {
if ($offset === null) { throw new Requests_Exception('Object is a dictionary, not a list', 'invalidset');
throw new Exception('Object is a dictionary, not a list', 'invalidset');
} }
if (is_string($offset)) { $key = strtolower($key);
$offset = strtolower($offset); $this->data[$key] = $value;
}
$this->data[$offset] = $value;
} }
/** /**
* Unset the given header * Unset the given header
* *
* @param string $offset * @param string $key
*/ */
#[ReturnTypeWillChange] public function offsetUnset($key) {
public function offsetUnset($offset) { unset($this->data[strtolower($key)]);
if (is_string($offset)) {
$offset = strtolower($offset);
}
unset($this->data[$offset]);
} }
/** /**
* Get an iterator for the data * Get an iterator for the data
* *
* @return \ArrayIterator * @return ArrayIterator
*/ */
#[ReturnTypeWillChange]
public function getIterator() { public function getIterator() {
return new ArrayIterator($this->data); return new ArrayIterator($this->data);
} }

View File

@ -2,68 +2,41 @@
/** /**
* Iterator for arrays requiring filtered values * Iterator for arrays requiring filtered values
* *
* @package Requests\Utilities * @package Requests
* @subpackage Utilities
*/ */
namespace WpOrg\Requests\Utility;
use ArrayIterator;
use ReturnTypeWillChange;
use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\Utility\InputValidator;
/** /**
* Iterator for arrays requiring filtered values * Iterator for arrays requiring filtered values
* *
* @package Requests\Utilities * @package Requests
* @subpackage Utilities
*/ */
final class FilteredIterator extends ArrayIterator { class Requests_Utility_FilteredIterator extends ArrayIterator {
/** /**
* Callback to run as a filter * Callback to run as a filter
* *
* @var callable * @var callable
*/ */
private $callback; protected $callback;
/** /**
* Create a new iterator * Create a new iterator
* *
* @param array $data * @param array $data
* @param callable $callback Callback to be called on each value * @param callable $callback Callback to be called on each value
*
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $data argument is not iterable.
*/ */
public function __construct($data, $callback) { public function __construct($data, $callback) {
if (InputValidator::is_iterable($data) === false) {
throw InvalidArgument::create(1, '$data', 'iterable', gettype($data));
}
parent::__construct($data); parent::__construct($data);
if (is_callable($callback)) {
$this->callback = $callback; $this->callback = $callback;
} }
}
/**
* @inheritdoc
*
* @phpcs:disable PHPCompatibility.FunctionNameRestrictions.NewMagicMethods.__unserializeFound
*/
#[ReturnTypeWillChange]
public function __unserialize($data) {}
// phpcs:enable
public function __wakeup() {
unset($this->callback);
}
/** /**
* Get the current item's value after filtering * Get the current item's value after filtering
* *
* @return string * @return string
*/ */
#[ReturnTypeWillChange]
public function current() { public function current() {
$value = parent::current(); $value = parent::current();
@ -77,6 +50,16 @@ final class FilteredIterator extends ArrayIterator {
/** /**
* @inheritdoc * @inheritdoc
*/ */
#[ReturnTypeWillChange] public function unserialize($serialized) {}
public function unserialize($data) {}
/**
* @inheritdoc
*
* @phpcs:disable PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.MethodDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.NewMagicMethods.__unserializeFound
*/
public function __unserialize($serialized) {}
public function __wakeup() {
unset($this->callback);
}
} }

View File

@ -1,109 +0,0 @@
<?php
/**
* Input validation utilities.
*
* @package Requests\Utilities
*/
namespace 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;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -12,9 +12,9 @@
* *
* @since 4.7.0 * @since 4.7.0
* *
* @see WpOrg\Requests\Hooks * @see Requests_Hooks
*/ */
class WP_HTTP_Requests_Hooks extends WpOrg\Requests\Hooks { class WP_HTTP_Requests_Hooks extends Requests_Hooks {
/** /**
* Requested URL. * Requested URL.
* *

View File

@ -8,7 +8,7 @@
*/ */
/** /**
* Core wrapper object for a WpOrg\Requests\Response for standardisation. * Core wrapper object for a Requests_Response for standardisation.
* *
* @since 4.6.0 * @since 4.6.0
* *
@ -19,7 +19,7 @@ class WP_HTTP_Requests_Response extends WP_HTTP_Response {
* Requests Response object. * Requests Response object.
* *
* @since 4.6.0 * @since 4.6.0
* @var \WpOrg\Requests\Response * @var Requests_Response
*/ */
protected $response; protected $response;
@ -36,10 +36,10 @@ class WP_HTTP_Requests_Response extends WP_HTTP_Response {
* *
* @since 4.6.0 * @since 4.6.0
* *
* @param \WpOrg\Requests\Response $response HTTP response. * @param Requests_Response $response HTTP response.
* @param string $filename Optional. File name. Default empty. * @param string $filename Optional. File name. Default empty.
*/ */
public function __construct( WpOrg\Requests\Response $response, $filename = '' ) { public function __construct( Requests_Response $response, $filename = '' ) {
$this->response = $response; $this->response = $response;
$this->filename = $filename; $this->filename = $filename;
} }
@ -49,7 +49,7 @@ class WP_HTTP_Requests_Response extends WP_HTTP_Response {
* *
* @since 4.6.0 * @since 4.6.0
* *
* @return WpOrg\Requests\Response HTTP response. * @return Requests_Response HTTP response.
*/ */
public function get_response_object() { public function get_response_object() {
return $this->response; return $this->response;
@ -60,11 +60,11 @@ class WP_HTTP_Requests_Response extends WP_HTTP_Response {
* *
* @since 4.6.0 * @since 4.6.0
* *
* @return \WpOrg\Requests\Utility\CaseInsensitiveDictionary Map of header name to header value. * @return \Requests_Utility_CaseInsensitiveDictionary Map of header name to header value.
*/ */
public function get_headers() { public function get_headers() {
// Ensure headers remain case-insensitive. // Ensure headers remain case-insensitive.
$converted = new WpOrg\Requests\Utility\CaseInsensitiveDictionary(); $converted = new Requests_Utility_CaseInsensitiveDictionary();
foreach ( $this->response->headers->getAll() as $key => $value ) { foreach ( $this->response->headers->getAll() as $key => $value ) {
if ( count( $value ) === 1 ) { if ( count( $value ) === 1 ) {
@ -85,7 +85,7 @@ class WP_HTTP_Requests_Response extends WP_HTTP_Response {
* @param array $headers Map of header name to header value. * @param array $headers Map of header name to header value.
*/ */
public function set_headers( $headers ) { public function set_headers( $headers ) {
$this->response->headers = new WpOrg\Requests\Response\Headers( $headers ); $this->response->headers = new Requests_Response_Headers( $headers );
} }
/** /**

View File

@ -7,11 +7,11 @@
* @since 2.7.0 * @since 2.7.0
*/ */
if ( ! class_exists( 'WpOrg\Requests\Autoload' ) ) { if ( ! class_exists( 'Requests' ) ) {
require ABSPATH . WPINC . '/Requests/Autoload.php'; require ABSPATH . WPINC . '/class-requests.php';
WpOrg\Requests\Autoload::register(); Requests::register_autoloader();
WpOrg\Requests\Requests::set_certificate_path( ABSPATH . WPINC . '/certificates/ca-bundle.crt' ); Requests::set_certificate_path( ABSPATH . WPINC . '/certificates/ca-bundle.crt' );
} }
/** /**
@ -274,14 +274,14 @@ class WP_Http {
if ( empty( $url ) || empty( $parsed_url['scheme'] ) ) { if ( empty( $url ) || empty( $parsed_url['scheme'] ) ) {
$response = new WP_Error( 'http_request_failed', __( 'A valid URL was not provided.' ) ); $response = new WP_Error( 'http_request_failed', __( 'A valid URL was not provided.' ) );
/** This action is documented in wp-includes/class-wp-http.php */ /** This action is documented in wp-includes/class-wp-http.php */
do_action( 'http_api_debug', $response, 'response', 'WpOrg\Requests\Requests', $parsed_args, $url ); do_action( 'http_api_debug', $response, 'response', 'Requests', $parsed_args, $url );
return $response; return $response;
} }
if ( $this->block_request( $url ) ) { if ( $this->block_request( $url ) ) {
$response = new WP_Error( 'http_request_not_executed', __( 'User has blocked requests through HTTP.' ) ); $response = new WP_Error( 'http_request_not_executed', __( 'User has blocked requests through HTTP.' ) );
/** This action is documented in wp-includes/class-wp-http.php */ /** This action is documented in wp-includes/class-wp-http.php */
do_action( 'http_api_debug', $response, 'response', 'WpOrg\Requests\Requests', $parsed_args, $url ); do_action( 'http_api_debug', $response, 'response', 'Requests', $parsed_args, $url );
return $response; return $response;
} }
@ -298,7 +298,7 @@ class WP_Http {
if ( ! wp_is_writable( dirname( $parsed_args['filename'] ) ) ) { if ( ! wp_is_writable( dirname( $parsed_args['filename'] ) ) ) {
$response = new WP_Error( 'http_request_failed', __( 'Destination directory for file streaming does not exist or is not writable.' ) ); $response = new WP_Error( 'http_request_failed', __( 'Destination directory for file streaming does not exist or is not writable.' ) );
/** This action is documented in wp-includes/class-wp-http.php */ /** This action is documented in wp-includes/class-wp-http.php */
do_action( 'http_api_debug', $response, 'response', 'WpOrg\Requests\Requests', $parsed_args, $url ); do_action( 'http_api_debug', $response, 'response', 'Requests', $parsed_args, $url );
return $response; return $response;
} }
} }
@ -346,7 +346,7 @@ class WP_Http {
$options['max_bytes'] = $parsed_args['limit_response_size']; $options['max_bytes'] = $parsed_args['limit_response_size'];
} }
// If we've got cookies, use and convert them to WpOrg\Requests\Cookie. // If we've got cookies, use and convert them to Requests_Cookie.
if ( ! empty( $parsed_args['cookies'] ) ) { if ( ! empty( $parsed_args['cookies'] ) ) {
$options['cookies'] = WP_Http::normalize_cookies( $parsed_args['cookies'] ); $options['cookies'] = WP_Http::normalize_cookies( $parsed_args['cookies'] );
} }
@ -378,7 +378,7 @@ class WP_Http {
// Check for proxies. // Check for proxies.
$proxy = new WP_HTTP_Proxy(); $proxy = new WP_HTTP_Proxy();
if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) { if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
$options['proxy'] = new WpOrg\Requests\Proxy\HTTP( $proxy->host() . ':' . $proxy->port() ); $options['proxy'] = new Requests_Proxy_HTTP( $proxy->host() . ':' . $proxy->port() );
if ( $proxy->use_authentication() ) { if ( $proxy->use_authentication() ) {
$options['proxy']->use_authentication = true; $options['proxy']->use_authentication = true;
@ -391,7 +391,7 @@ class WP_Http {
mbstring_binary_safe_encoding(); mbstring_binary_safe_encoding();
try { try {
$requests_response = WpOrg\Requests\Requests::request( $url, $headers, $data, $type, $options ); $requests_response = Requests::request( $url, $headers, $data, $type, $options );
// Convert the response into an array. // Convert the response into an array.
$http_response = new WP_HTTP_Requests_Response( $requests_response, $parsed_args['filename'] ); $http_response = new WP_HTTP_Requests_Response( $requests_response, $parsed_args['filename'] );
@ -399,7 +399,7 @@ class WP_Http {
// Add the original object to the array. // Add the original object to the array.
$response['http_response'] = $http_response; $response['http_response'] = $http_response;
} catch ( WpOrg\Requests\Exception $e ) { } catch ( Requests_Exception $e ) {
$response = new WP_Error( 'http_request_failed', $e->getMessage() ); $response = new WP_Error( 'http_request_failed', $e->getMessage() );
} }
@ -416,7 +416,7 @@ class WP_Http {
* @param array $parsed_args HTTP request arguments. * @param array $parsed_args HTTP request arguments.
* @param string $url The request URL. * @param string $url The request URL.
*/ */
do_action( 'http_api_debug', $response, 'response', 'WpOrg\Requests\Requests', $parsed_args, $url ); do_action( 'http_api_debug', $response, 'response', 'Requests', $parsed_args, $url );
if ( is_wp_error( $response ) ) { if ( is_wp_error( $response ) ) {
return $response; return $response;
} }
@ -452,10 +452,10 @@ class WP_Http {
* @since 4.6.0 * @since 4.6.0
* *
* @param array $cookies Array of cookies to send with the request. * @param array $cookies Array of cookies to send with the request.
* @return WpOrg\Requests\Cookie\Jar Cookie holder object. * @return Requests_Cookie_Jar Cookie holder object.
*/ */
public static function normalize_cookies( $cookies ) { public static function normalize_cookies( $cookies ) {
$cookie_jar = new WpOrg\Requests\Cookie\Jar(); $cookie_jar = new Requests_Cookie_Jar();
foreach ( $cookies as $name => $value ) { foreach ( $cookies as $name => $value ) {
if ( $value instanceof WP_Http_Cookie ) { if ( $value instanceof WP_Http_Cookie ) {
@ -465,9 +465,9 @@ class WP_Http {
return null !== $attr; return null !== $attr;
} }
); );
$cookie_jar[ $value->name ] = new WpOrg\Requests\Cookie( $value->name, $value->value, $attributes, array( 'host-only' => $value->host_only ) ); $cookie_jar[ $value->name ] = new Requests_Cookie( $value->name, $value->value, $attributes, array( 'host-only' => $value->host_only ) );
} elseif ( is_scalar( $value ) ) { } elseif ( is_scalar( $value ) ) {
$cookie_jar[ $name ] = new WpOrg\Requests\Cookie( $name, (string) $value ); $cookie_jar[ $name ] = new Requests_Cookie( $name, $value );
} }
} }
@ -487,12 +487,12 @@ class WP_Http {
* @param array $headers Headers for the redirect. * @param array $headers Headers for the redirect.
* @param string|array $data Body to send with the request. * @param string|array $data Body to send with the request.
* @param array $options Redirect request options. * @param array $options Redirect request options.
* @param WpOrg\Requests\Response $original Response object. * @param Requests_Response $original Response object.
*/ */
public static function browser_redirect_compatibility( $location, $headers, $data, &$options, $original ) { public static function browser_redirect_compatibility( $location, $headers, $data, &$options, $original ) {
// Browser compatibility. // Browser compatibility.
if ( 302 === $original->status_code ) { if ( 302 === $original->status_code ) {
$options['type'] = WpOrg\Requests\Requests::GET; $options['type'] = Requests::GET;
} }
} }
@ -501,12 +501,12 @@ class WP_Http {
* *
* @since 4.7.5 * @since 4.7.5
* *
* @throws WpOrg\Requests\Exception On unsuccessful URL validation. * @throws Requests_Exception On unsuccessful URL validation.
* @param string $location URL to redirect to. * @param string $location URL to redirect to.
*/ */
public static function validate_redirects( $location ) { public static function validate_redirects( $location ) {
if ( ! wp_http_validate_url( $location ) ) { if ( ! wp_http_validate_url( $location ) ) {
throw new WpOrg\Requests\Exception( __( 'A valid URL was not provided.' ), 'wp_http.redirect_failed_validation' ); throw new Requests_Exception( __( 'A valid URL was not provided.' ), 'wp_http.redirect_failed_validation' );
} }
} }

View File

@ -200,12 +200,12 @@ function wp_remote_head( $url, $args = array() ) {
* Retrieve only the headers from the raw response. * Retrieve only the headers from the raw response.
* *
* @since 2.7.0 * @since 2.7.0
* @since 4.6.0 Return value changed from an array to an WpOrg\Requests\Utility\CaseInsensitiveDictionary instance. * @since 4.6.0 Return value changed from an array to an Requests_Utility_CaseInsensitiveDictionary instance.
* *
* @see \WpOrg\Requests\Utility\CaseInsensitiveDictionary * @see \Requests_Utility_CaseInsensitiveDictionary
* *
* @param array|WP_Error $response HTTP response. * @param array|WP_Error $response HTTP response.
* @return array|\WpOrg\Requests\Utility\CaseInsensitiveDictionary The headers of the response. Empty array if incorrect parameter given. * @return array|\Requests_Utility_CaseInsensitiveDictionary The headers of the response. Empty array if incorrect parameter given.
*/ */
function wp_remote_retrieve_headers( $response ) { function wp_remote_retrieve_headers( $response ) {
if ( is_wp_error( $response ) || ! isset( $response['headers'] ) ) { if ( is_wp_error( $response ) || ! isset( $response['headers'] ) ) {

View File

@ -1390,7 +1390,7 @@ function rest_parse_request_arg( $value, $request, $param ) {
function rest_is_ip_address( $ip ) { function rest_is_ip_address( $ip ) {
$ipv4_pattern = '/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/'; $ipv4_pattern = '/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/';
if ( ! preg_match( $ipv4_pattern, $ip ) && ! WpOrg\Requests\Ipv6::check_ipv6( $ip ) ) { if ( ! preg_match( $ipv4_pattern, $ip ) && ! Requests_IPv6::check_ipv6( $ip ) ) {
return false; return false;
} }

View File

@ -16,7 +16,7 @@
* *
* @global string $wp_version * @global string $wp_version
*/ */
$wp_version = '5.9-beta1-52327'; $wp_version = '5.9-beta1-52328';
/** /**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema. * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.