WordPress / Requests
Showing 26 of 169 files from the diff.
Other files ignored by Codecov
examples/get.php has changed.
.gitignore has changed.
CHANGELOG.md has changed.
tests/Cookies.php was deleted.
.codecov.yml has changed.
phpunit.xml.dist has changed.
docs/hooks.md has changed.
docs/README.md has changed.
tests/Session.php was deleted.
README.md has changed.
.phpcs.xml.dist has changed.
examples/post.php has changed.
tests/IriTest.php has changed.
docs/usage.md has changed.
tests/SSL.php was deleted.
docs/proxy.md has changed.
.gitattributes has changed.
composer.json has changed.

@@ -0,0 +1,1079 @@
Loading
1 +
<?php
2 +
/**
3 +
 * Requests for PHP
4 +
 *
5 +
 * Inspired by Requests for Python.
6 +
 *
7 +
 * Based on concepts from SimplePie_File, RequestCore and WP_Http.
8 +
 *
9 +
 * @package Requests
10 +
 */
11 +
12 +
namespace WpOrg\Requests;
13 +
14 +
use WpOrg\Requests\Auth\Basic;
15 +
use WpOrg\Requests\Capability;
16 +
use WpOrg\Requests\Cookie\Jar;
17 +
use WpOrg\Requests\Exception;
18 +
use WpOrg\Requests\Exception\InvalidArgument;
19 +
use WpOrg\Requests\Hooks;
20 +
use WpOrg\Requests\IdnaEncoder;
21 +
use WpOrg\Requests\Iri;
22 +
use WpOrg\Requests\Proxy\Http;
23 +
use WpOrg\Requests\Response;
24 +
use WpOrg\Requests\Transport\Curl;
25 +
use WpOrg\Requests\Transport\Fsockopen;
26 +
use WpOrg\Requests\Utility\InputValidator;
27 +
28 +
/**
29 +
 * Requests for PHP
30 +
 *
31 +
 * Inspired by Requests for Python.
32 +
 *
33 +
 * Based on concepts from SimplePie_File, RequestCore and WP_Http.
34 +
 *
35 +
 * @package Requests
36 +
 */
37 +
class Requests {
38 +
	/**
39 +
	 * POST method
40 +
	 *
41 +
	 * @var string
42 +
	 */
43 +
	const POST = 'POST';
44 +
45 +
	/**
46 +
	 * PUT method
47 +
	 *
48 +
	 * @var string
49 +
	 */
50 +
	const PUT = 'PUT';
51 +
52 +
	/**
53 +
	 * GET method
54 +
	 *
55 +
	 * @var string
56 +
	 */
57 +
	const GET = 'GET';
58 +
59 +
	/**
60 +
	 * HEAD method
61 +
	 *
62 +
	 * @var string
63 +
	 */
64 +
	const HEAD = 'HEAD';
65 +
66 +
	/**
67 +
	 * DELETE method
68 +
	 *
69 +
	 * @var string
70 +
	 */
71 +
	const DELETE = 'DELETE';
72 +
73 +
	/**
74 +
	 * OPTIONS method
75 +
	 *
76 +
	 * @var string
77 +
	 */
78 +
	const OPTIONS = 'OPTIONS';
79 +
80 +
	/**
81 +
	 * TRACE method
82 +
	 *
83 +
	 * @var string
84 +
	 */
85 +
	const TRACE = 'TRACE';
86 +
87 +
	/**
88 +
	 * PATCH method
89 +
	 *
90 +
	 * @link https://tools.ietf.org/html/rfc5789
91 +
	 * @var string
92 +
	 */
93 +
	const PATCH = 'PATCH';
94 +
95 +
	/**
96 +
	 * Default size of buffer size to read streams
97 +
	 *
98 +
	 * @var integer
99 +
	 */
100 +
	const BUFFER_SIZE = 1160;
101 +
102 +
	/**
103 +
	 * Option defaults.
104 +
	 *
105 +
	 * @see \WpOrg\Requests\Requests::get_default_options()
106 +
	 * @see \WpOrg\Requests\Requests::request() for values returned by this method
107 +
	 *
108 +
	 * @since 2.0.0
109 +
	 *
110 +
	 * @var array
111 +
	 */
112 +
	const OPTION_DEFAULTS = [
113 +
		'timeout'          => 10,
114 +
		'connect_timeout'  => 10,
115 +
		'useragent'        => 'php-requests/' . self::VERSION,
116 +
		'protocol_version' => 1.1,
117 +
		'redirected'       => 0,
118 +
		'redirects'        => 10,
119 +
		'follow_redirects' => true,
120 +
		'blocking'         => true,
121 +
		'type'             => self::GET,
122 +
		'filename'         => false,
123 +
		'auth'             => false,
124 +
		'proxy'            => false,
125 +
		'cookies'          => false,
126 +
		'max_bytes'        => false,
127 +
		'idn'              => true,
128 +
		'hooks'            => null,
129 +
		'transport'        => null,
130 +
		'verify'           => null,
131 +
		'verifyname'       => true,
132 +
	];
133 +
134 +
	/**
135 +
	 * Default supported Transport classes.
136 +
	 *
137 +
	 * @since 2.0.0
138 +
	 *
139 +
	 * @var array
140 +
	 */
141 +
	const DEFAULT_TRANSPORTS = [
142 +
		Curl::class      => Curl::class,
143 +
		Fsockopen::class => Fsockopen::class,
144 +
	];
145 +
146 +
	/**
147 +
	 * Current version of Requests
148 +
	 *
149 +
	 * @var string
150 +
	 */
151 +
	const VERSION = '2.0.0';
152 +
153 +
	/**
154 +
	 * Selected transport name
155 +
	 *
156 +
	 * Use {@see \WpOrg\Requests\Requests::get_transport()} instead
157 +
	 *
158 +
	 * @var array
159 +
	 */
160 +
	public static $transport = [];
161 +
162 +
	/**
163 +
	 * Registered transport classes
164 +
	 *
165 +
	 * @var array
166 +
	 */
167 +
	protected static $transports = [];
168 +
169 +
	/**
170 +
	 * Default certificate path.
171 +
	 *
172 +
	 * @see \WpOrg\Requests\Requests::get_certificate_path()
173 +
	 * @see \WpOrg\Requests\Requests::set_certificate_path()
174 +
	 *
175 +
	 * @var string
176 +
	 */
177 +
	protected static $certificate_path = __DIR__ . '/../certificates/cacert.pem';
178 +
179 +
	/**
180 +
	 * All (known) valid deflate, gzip header magic markers.
181 +
	 *
182 +
	 * These markers relate to different compression levels.
183 +
	 *
184 +
	 * @link https://stackoverflow.com/a/43170354/482864 Marker source.
185 +
	 *
186 +
	 * @since 2.0.0
187 +
	 *
188 +
	 * @var array
189 +
	 */
190 +
	private static $magic_compression_headers = [
191 +
		"\x1f\x8b" => true, // Gzip marker.
192 +
		"\x78\x01" => true, // Zlib marker - level 1.
193 +
		"\x78\x5e" => true, // Zlib marker - level 2 to 5.
194 +
		"\x78\x9c" => true, // Zlib marker - level 6.
195 +
		"\x78\xda" => true, // Zlib marker - level 7 to 9.
196 +
	];
197 +
198 +
	/**
199 +
	 * This is a static class, do not instantiate it
200 +
	 *
201 +
	 * @codeCoverageIgnore
202 +
	 */
203 +
	private function __construct() {}
204 +
205 +
	/**
206 +
	 * Register a transport
207 +
	 *
208 +
	 * @param string $transport Transport class to add, must support the \WpOrg\Requests\Transport interface
209 +
	 */
210 +
	public static function add_transport($transport) {
211 +
		if (empty(self::$transports)) {
212 +
			self::$transports = self::DEFAULT_TRANSPORTS;
213 +
		}
214 +
215 +
		self::$transports[$transport] = $transport;
216 +
	}
217 +
218 +
	/**
219 +
	 * Get the fully qualified class name (FQCN) for a working transport.
220 +
	 *
221 +
	 * @param array<string, bool> $capabilities Optional. Associative array of capabilities to test against, i.e. `['<capability>' => true]`.
222 +
	 * @return string FQCN of the transport to use, or an empty string if no transport was
223 +
	 *                found which provided the requested capabilities.
224 +
	 */
225 +
	protected static function get_transport_class(array $capabilities = []) {
226 +
		// Caching code, don't bother testing coverage.
227 +
		// @codeCoverageIgnoreStart
228 +
		// Array of capabilities as a string to be used as an array key.
229 +
		ksort($capabilities);
230 +
		$cap_string = serialize($capabilities);
231 +
232 +
		// Don't search for a transport if it's already been done for these $capabilities.
233 +
		if (isset(self::$transport[$cap_string])) {
234 +
			return self::$transport[$cap_string];
235 +
		}
236 +
237 +
		// Ensure we will not run this same check again later on.
238 +
		self::$transport[$cap_string] = '';
239 +
		// @codeCoverageIgnoreEnd
240 +
241 +
		if (empty(self::$transports)) {
242 +
			self::$transports = self::DEFAULT_TRANSPORTS;
243 +
		}
244 +
245 +
		// Find us a working transport.
246 +
		foreach (self::$transports as $class) {
247 +
			if (!class_exists($class)) {
248 +
				continue;
249 +
			}
250 +
251 +
			$result = $class::test($capabilities);
252 +
			if ($result === true) {
253 +
				self::$transport[$cap_string] = $class;
254 +
				break;
255 +
			}
256 +
		}
257 +
258 +
		return self::$transport[$cap_string];
259 +
	}
260 +
261 +
	/**
262 +
	 * Get a working transport.
263 +
	 *
264 +
	 * @param array<string, bool> $capabilities Optional. Associative array of capabilities to test against, i.e. `['<capability>' => true]`.
265 +
	 * @return \WpOrg\Requests\Transport
266 +
	 * @throws \WpOrg\Requests\Exception If no valid transport is found (`notransport`).
267 +
	 */
268 +
	protected static function get_transport(array $capabilities = []) {
269 +
		$class = self::get_transport_class($capabilities);
270 +
271 +
		if ($class === '') {
272 +
			throw new Exception('No working transports found', 'notransport', self::$transports);
273 +
		}
274 +
275 +
		return new $class();
276 +
	}
277 +
278 +
	/**
279 +
	 * Checks to see if we have a transport for the capabilities requested.
280 +
	 *
281 +
	 * Supported capabilities can be found in the {@see \WpOrg\Requests\Capability}
282 +
	 * interface as constants.
283 +
	 *
284 +
	 * Example usage:
285 +
	 * `Requests::has_capabilities([Capability::SSL => true])`.
286 +
	 *
287 +
	 * @param array<string, bool> $capabilities Optional. Associative array of capabilities to test against, i.e. `['<capability>' => true]`.
288 +
	 * @return bool Whether the transport has the requested capabilities.
289 +
	 */
290 +
	public static function has_capabilities(array $capabilities = []) {
291 +
		return self::get_transport_class($capabilities) !== '';
292 +
	}
293 +
294 +
	/**#@+
295 +
	 * @see \WpOrg\Requests\Requests::request()
296 +
	 * @param string $url
297 +
	 * @param array $headers
298 +
	 * @param array $options
299 +
	 * @return \WpOrg\Requests\Response
300 +
	 */
301 +
	/**
302 +
	 * Send a GET request
303 +
	 */
304 +
	public static function get($url, $headers = [], $options = []) {
305 +
		return self::request($url, $headers, null, self::GET, $options);
306 +
	}
307 +
308 +
	/**
309 +
	 * Send a HEAD request
310 +
	 */
311 +
	public static function head($url, $headers = [], $options = []) {
312 +
		return self::request($url, $headers, null, self::HEAD, $options);
313 +
	}
314 +
315 +
	/**
316 +
	 * Send a DELETE request
317 +
	 */
318 +
	public static function delete($url, $headers = [], $options = []) {
319 +
		return self::request($url, $headers, null, self::DELETE, $options);
320 +
	}
321 +
322 +
	/**
323 +
	 * Send a TRACE request
324 +
	 */
325 +
	public static function trace($url, $headers = [], $options = []) {
326 +
		return self::request($url, $headers, null, self::TRACE, $options);
327 +
	}
328 +
	/**#@-*/
329 +
330 +
	/**#@+
331 +
	 * @see \WpOrg\Requests\Requests::request()
332 +
	 * @param string $url
333 +
	 * @param array $headers
334 +
	 * @param array $data
335 +
	 * @param array $options
336 +
	 * @return \WpOrg\Requests\Response
337 +
	 */
338 +
	/**
339 +
	 * Send a POST request
340 +
	 */
341 +
	public static function post($url, $headers = [], $data = [], $options = []) {
342 +
		return self::request($url, $headers, $data, self::POST, $options);
343 +
	}
344 +
	/**
345 +
	 * Send a PUT request
346 +
	 */
347 +
	public static function put($url, $headers = [], $data = [], $options = []) {
348 +
		return self::request($url, $headers, $data, self::PUT, $options);
349 +
	}
350 +
351 +
	/**
352 +
	 * Send an OPTIONS request
353 +
	 */
354 +
	public static function options($url, $headers = [], $data = [], $options = []) {
355 +
		return self::request($url, $headers, $data, self::OPTIONS, $options);
356 +
	}
357 +
358 +
	/**
359 +
	 * Send a PATCH request
360 +
	 *
361 +
	 * Note: Unlike {@see \WpOrg\Requests\Requests::post()} and {@see \WpOrg\Requests\Requests::put()},
362 +
	 * `$headers` is required, as the specification recommends that should send an ETag
363 +
	 *
364 +
	 * @link https://tools.ietf.org/html/rfc5789
365 +
	 */
366 +
	public static function patch($url, $headers, $data = [], $options = []) {
367 +
		return self::request($url, $headers, $data, self::PATCH, $options);
368 +
	}
369 +
	/**#@-*/
370 +
371 +
	/**
372 +
	 * Main interface for HTTP requests
373 +
	 *
374 +
	 * This method initiates a request and sends it via a transport before
375 +
	 * parsing.
376 +
	 *
377 +
	 * The `$options` parameter takes an associative array with the following
378 +
	 * options:
379 +
	 *
380 +
	 * - `timeout`: How long should we wait for a response?
381 +
	 *    Note: for cURL, a minimum of 1 second applies, as DNS resolution
382 +
	 *    operates at second-resolution only.
383 +
	 *    (float, seconds with a millisecond precision, default: 10, example: 0.01)
384 +
	 * - `connect_timeout`: How long should we wait while trying to connect?
385 +
	 *    (float, seconds with a millisecond precision, default: 10, example: 0.01)
386 +
	 * - `useragent`: Useragent to send to the server
387 +
	 *    (string, default: php-requests/$version)
388 +
	 * - `follow_redirects`: Should we follow 3xx redirects?
389 +
	 *    (boolean, default: true)
390 +
	 * - `redirects`: How many times should we redirect before erroring?
391 +
	 *    (integer, default: 10)
392 +
	 * - `blocking`: Should we block processing on this request?
393 +
	 *    (boolean, default: true)
394 +
	 * - `filename`: File to stream the body to instead.
395 +
	 *    (string|boolean, default: false)
396 +
	 * - `auth`: Authentication handler or array of user/password details to use
397 +
	 *    for Basic authentication
398 +
	 *    (\WpOrg\Requests\Auth|array|boolean, default: false)
399 +
	 * - `proxy`: Proxy details to use for proxy by-passing and authentication
400 +
	 *    (\WpOrg\Requests\Proxy|array|string|boolean, default: false)
401 +
	 * - `max_bytes`: Limit for the response body size.
402 +
	 *    (integer|boolean, default: false)
403 +
	 * - `idn`: Enable IDN parsing
404 +
	 *    (boolean, default: true)
405 +
	 * - `transport`: Custom transport. Either a class name, or a
406 +
	 *    transport object. Defaults to the first working transport from
407 +
	 *    {@see \WpOrg\Requests\Requests::getTransport()}
408 +
	 *    (string|\WpOrg\Requests\Transport, default: {@see \WpOrg\Requests\Requests::getTransport()})
409 +
	 * - `hooks`: Hooks handler.
410 +
	 *    (\WpOrg\Requests\HookManager, default: new WpOrg\Requests\Hooks())
411 +
	 * - `verify`: Should we verify SSL certificates? Allows passing in a custom
412 +
	 *    certificate file as a string. (Using true uses the system-wide root
413 +
	 *    certificate store instead, but this may have different behaviour
414 +
	 *    across transports.)
415 +
	 *    (string|boolean, default: certificates/cacert.pem)
416 +
	 * - `verifyname`: Should we verify the common name in the SSL certificate?
417 +
	 *    (boolean, default: true)
418 +
	 * - `data_format`: How should we send the `$data` parameter?
419 +
	 *    (string, one of 'query' or 'body', default: 'query' for
420 +
	 *    HEAD/GET/DELETE, 'body' for POST/PUT/OPTIONS/PATCH)
421 +
	 *
422 +
	 * @param string|Stringable $url URL to request
423 +
	 * @param array $headers Extra headers to send with the request
424 +
	 * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
425 +
	 * @param string $type HTTP request type (use Requests constants)
426 +
	 * @param array $options Options for the request (see description for more information)
427 +
	 * @return \WpOrg\Requests\Response
428 +
	 *
429 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string or Stringable.
430 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $type argument is not a string.
431 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
432 +
	 * @throws \WpOrg\Requests\Exception On invalid URLs (`nonhttp`)
433 +
	 */
434 +
	public static function request($url, $headers = [], $data = [], $type = self::GET, $options = []) {
435 +
		if (InputValidator::is_string_or_stringable($url) === false) {
436 +
			throw InvalidArgument::create(1, '$url', 'string|Stringable', gettype($url));
437 +
		}
438 +
439 +
		if (is_string($type) === false) {
440 +
			throw InvalidArgument::create(4, '$type', 'string', gettype($type));
441 +
		}
442 +
443 +
		if (is_array($options) === false) {
444 +
			throw InvalidArgument::create(5, '$options', 'array', gettype($options));
445 +
		}
446 +
447 +
		if (empty($options['type'])) {
448 +
			$options['type'] = $type;
449 +
		}
450 +
		$options = array_merge(self::get_default_options(), $options);
451 +
452 +
		self::set_defaults($url, $headers, $data, $type, $options);
453 +
454 +
		$options['hooks']->dispatch('requests.before_request', [&$url, &$headers, &$data, &$type, &$options]);
455 +
456 +
		if (!empty($options['transport'])) {
457 +
			$transport = $options['transport'];
458 +
459 +
			if (is_string($options['transport'])) {
460 +
				$transport = new $transport();
461 +
			}
462 +
		}
463 +
		else {
464 +
			$need_ssl     = (stripos($url, 'https://') === 0);
465 +
			$capabilities = [Capability::SSL => $need_ssl];
466 +
			$transport    = self::get_transport($capabilities);
467 +
		}
468 +
		$response = $transport->request($url, $headers, $data, $options);
469 +
470 +
		$options['hooks']->dispatch('requests.before_parse', [&$response, $url, $headers, $data, $type, $options]);
471 +
472 +
		return self::parse_response($response, $url, $headers, $data, $options);
473 +
	}
474 +
475 +
	/**
476 +
	 * Send multiple HTTP requests simultaneously
477 +
	 *
478 +
	 * The `$requests` parameter takes an associative or indexed array of
479 +
	 * request fields. The key of each request can be used to match up the
480 +
	 * request with the returned data, or with the request passed into your
481 +
	 * `multiple.request.complete` callback.
482 +
	 *
483 +
	 * The request fields value is an associative array with the following keys:
484 +
	 *
485 +
	 * - `url`: Request URL Same as the `$url` parameter to
486 +
	 *    {@see \WpOrg\Requests\Requests::request()}
487 +
	 *    (string, required)
488 +
	 * - `headers`: Associative array of header fields. Same as the `$headers`
489 +
	 *    parameter to {@see \WpOrg\Requests\Requests::request()}
490 +
	 *    (array, default: `array()`)
491 +
	 * - `data`: Associative array of data fields or a string. Same as the
492 +
	 *    `$data` parameter to {@see \WpOrg\Requests\Requests::request()}
493 +
	 *    (array|string, default: `array()`)
494 +
	 * - `type`: HTTP request type (use \WpOrg\Requests\Requests constants). Same as the `$type`
495 +
	 *    parameter to {@see \WpOrg\Requests\Requests::request()}
496 +
	 *    (string, default: `\WpOrg\Requests\Requests::GET`)
497 +
	 * - `cookies`: Associative array of cookie name to value, or cookie jar.
498 +
	 *    (array|\WpOrg\Requests\Cookie\Jar)
499 +
	 *
500 +
	 * If the `$options` parameter is specified, individual requests will
501 +
	 * inherit options from it. This can be used to use a single hooking system,
502 +
	 * or set all the types to `\WpOrg\Requests\Requests::POST`, for example.
503 +
	 *
504 +
	 * In addition, the `$options` parameter takes the following global options:
505 +
	 *
506 +
	 * - `complete`: A callback for when a request is complete. Takes two
507 +
	 *    parameters, a \WpOrg\Requests\Response/\WpOrg\Requests\Exception reference, and the
508 +
	 *    ID from the request array (Note: this can also be overridden on a
509 +
	 *    per-request basis, although that's a little silly)
510 +
	 *    (callback)
511 +
	 *
512 +
	 * @param array $requests Requests data (see description for more information)
513 +
	 * @param array $options Global and default options (see {@see \WpOrg\Requests\Requests::request()})
514 +
	 * @return array Responses (either \WpOrg\Requests\Response or a \WpOrg\Requests\Exception object)
515 +
	 *
516 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access.
517 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
518 +
	 */
519 +
	public static function request_multiple($requests, $options = []) {
520 +
		if (InputValidator::has_array_access($requests) === false || InputValidator::is_iterable($requests) === false) {
521 +
			throw InvalidArgument::create(1, '$requests', 'array|ArrayAccess&Traversable', gettype($requests));
522 +
		}
523 +
524 +
		if (is_array($options) === false) {
525 +
			throw InvalidArgument::create(2, '$options', 'array', gettype($options));
526 +
		}
527 +
528 +
		$options = array_merge(self::get_default_options(true), $options);
529 +
530 +
		if (!empty($options['hooks'])) {
531 +
			$options['hooks']->register('transport.internal.parse_response', [static::class, 'parse_multiple']);
532 +
			if (!empty($options['complete'])) {
533 +
				$options['hooks']->register('multiple.request.complete', $options['complete']);
534 +
			}
535 +
		}
536 +
537 +
		foreach ($requests as $id => &$request) {
538 +
			if (!isset($request['headers'])) {
539 +
				$request['headers'] = [];
540 +
			}
541 +
			if (!isset($request['data'])) {
542 +
				$request['data'] = [];
543 +
			}
544 +
			if (!isset($request['type'])) {
545 +
				$request['type'] = self::GET;
546 +
			}
547 +
			if (!isset($request['options'])) {
548 +
				$request['options']         = $options;
549 +
				$request['options']['type'] = $request['type'];
550 +
			}
551 +
			else {
552 +
				if (empty($request['options']['type'])) {
553 +
					$request['options']['type'] = $request['type'];
554 +
				}
555 +
				$request['options'] = array_merge($options, $request['options']);
556 +
			}
557 +
558 +
			self::set_defaults($request['url'], $request['headers'], $request['data'], $request['type'], $request['options']);
559 +
560 +
			// Ensure we only hook in once
561 +
			if ($request['options']['hooks'] !== $options['hooks']) {
562 +
				$request['options']['hooks']->register('transport.internal.parse_response', [static::class, 'parse_multiple']);
563 +
				if (!empty($request['options']['complete'])) {
564 +
					$request['options']['hooks']->register('multiple.request.complete', $request['options']['complete']);
565 +
				}
566 +
			}
567 +
		}
568 +
		unset($request);
569 +
570 +
		if (!empty($options['transport'])) {
571 +
			$transport = $options['transport'];
572 +
573 +
			if (is_string($options['transport'])) {
574 +
				$transport = new $transport();
575 +
			}
576 +
		}
577 +
		else {
578 +
			$transport = self::get_transport();
579 +
		}
580 +
		$responses = $transport->request_multiple($requests, $options);
581 +
582 +
		foreach ($responses as $id => &$response) {
583 +
			// If our hook got messed with somehow, ensure we end up with the
584 +
			// correct response
585 +
			if (is_string($response)) {
586 +
				$request = $requests[$id];
587 +
				self::parse_multiple($response, $request);
588 +
				$request['options']['hooks']->dispatch('multiple.request.complete', [&$response, $id]);
589 +
			}
590 +
		}
591 +
592 +
		return $responses;
593 +
	}
594 +
595 +
	/**
596 +
	 * Get the default options
597 +
	 *
598 +
	 * @see \WpOrg\Requests\Requests::request() for values returned by this method
599 +
	 * @param boolean $multirequest Is this a multirequest?
600 +
	 * @return array Default option values
601 +
	 */
602 +
	protected static function get_default_options($multirequest = false) {
603 +
		$defaults           = static::OPTION_DEFAULTS;
604 +
		$defaults['verify'] = self::$certificate_path;
605 +
606 +
		if ($multirequest !== false) {
607 +
			$defaults['complete'] = null;
608 +
		}
609 +
		return $defaults;
610 +
	}
611 +
612 +
	/**
613 +
	 * Get default certificate path.
614 +
	 *
615 +
	 * @return string Default certificate path.
616 +
	 */
617 +
	public static function get_certificate_path() {
618 +
		return self::$certificate_path;
619 +
	}
620 +
621 +
	/**
622 +
	 * Set default certificate path.
623 +
	 *
624 +
	 * @param string|Stringable|bool $path Certificate path, pointing to a PEM file.
625 +
	 *
626 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string, Stringable or boolean.
627 +
	 */
628 +
	public static function set_certificate_path($path) {
629 +
		if (InputValidator::is_string_or_stringable($path) === false && is_bool($path) === false) {
630 +
			throw InvalidArgument::create(1, '$path', 'string|Stringable|bool', gettype($path));
631 +
		}
632 +
633 +
		self::$certificate_path = $path;
634 +
	}
635 +
636 +
	/**
637 +
	 * Set the default values
638 +
	 *
639 +
	 * @param string $url URL to request
640 +
	 * @param array $headers Extra headers to send with the request
641 +
	 * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
642 +
	 * @param string $type HTTP request type
643 +
	 * @param array $options Options for the request
644 +
	 * @return void $options is updated with the results
645 +
	 *
646 +
	 * @throws \WpOrg\Requests\Exception When the $url is not an http(s) URL.
647 +
	 */
648 +
	protected static function set_defaults(&$url, &$headers, &$data, &$type, &$options) {
649 +
		if (!preg_match('/^http(s)?:\/\//i', $url, $matches)) {
650 +
			throw new Exception('Only HTTP(S) requests are handled.', 'nonhttp', $url);
651 +
		}
652 +
653 +
		if (empty($options['hooks'])) {
654 +
			$options['hooks'] = new Hooks();
655 +
		}
656 +
657 +
		if (is_array($options['auth'])) {
658 +
			$options['auth'] = new Basic($options['auth']);
659 +
		}
660 +
		if ($options['auth'] !== false) {
661 +
			$options['auth']->register($options['hooks']);
662 +
		}
663 +
664 +
		if (is_string($options['proxy']) || is_array($options['proxy'])) {
665 +
			$options['proxy'] = new Http($options['proxy']);
666 +
		}
667 +
		if ($options['proxy'] !== false) {
668 +
			$options['proxy']->register($options['hooks']);
669 +
		}
670 +
671 +
		if (is_array($options['cookies'])) {
672 +
			$options['cookies'] = new Jar($options['cookies']);
673 +
		}
674 +
		elseif (empty($options['cookies'])) {
675 +
			$options['cookies'] = new Jar();
676 +
		}
677 +
		if ($options['cookies'] !== false) {
678 +
			$options['cookies']->register($options['hooks']);
679 +
		}
680 +
681 +
		if ($options['idn'] !== false) {
682 +
			$iri       = new Iri($url);
683 +
			$iri->host = IdnaEncoder::encode($iri->ihost);
684 +
			$url       = $iri->uri;
685 +
		}
686 +
687 +
		// Massage the type to ensure we support it.
688 +
		$type = strtoupper($type);
689 +
690 +
		if (!isset($options['data_format'])) {
691 +
			if (in_array($type, [self::HEAD, self::GET, self::DELETE], true)) {
692 +
				$options['data_format'] = 'query';
693 +
			}
694 +
			else {
695 +
				$options['data_format'] = 'body';
696 +
			}
697 +
		}
698 +
	}
699 +
700 +
	/**
701 +
	 * HTTP response parser
702 +
	 *
703 +
	 * @param string $headers Full response text including headers and body
704 +
	 * @param string $url Original request URL
705 +
	 * @param array $req_headers Original $headers array passed to {@link request()}, in case we need to follow redirects
706 +
	 * @param array $req_data Original $data array passed to {@link request()}, in case we need to follow redirects
707 +
	 * @param array $options Original $options array passed to {@link request()}, in case we need to follow redirects
708 +
	 * @return \WpOrg\Requests\Response
709 +
	 *
710 +
	 * @throws \WpOrg\Requests\Exception On missing head/body separator (`requests.no_crlf_separator`)
711 +
	 * @throws \WpOrg\Requests\Exception On missing head/body separator (`noversion`)
712 +
	 * @throws \WpOrg\Requests\Exception On missing head/body separator (`toomanyredirects`)
713 +
	 */
714 +
	protected static function parse_response($headers, $url, $req_headers, $req_data, $options) {
715 +
		$return = new Response();
716 +
		if (!$options['blocking']) {
717 +
			return $return;
718 +
		}
719 +
720 +
		$return->raw  = $headers;
721 +
		$return->url  = (string) $url;
722 +
		$return->body = '';
723 +
724 +
		if (!$options['filename']) {
725 +
			$pos = strpos($headers, "\r\n\r\n");
726 +
			if ($pos === false) {
727 +
				// Crap!
728 +
				throw new Exception('Missing header/body separator', 'requests.no_crlf_separator');
729 +
			}
730 +
731 +
			$headers = substr($return->raw, 0, $pos);
732 +
			// Headers will always be separated from the body by two new lines - `\n\r\n\r`.
733 +
			$body = substr($return->raw, $pos + 4);
734 +
			if (!empty($body)) {
735 +
				$return->body = $body;
736 +
			}
737 +
		}
738 +
		// Pretend CRLF = LF for compatibility (RFC 2616, section 19.3)
739 +
		$headers = str_replace("\r\n", "\n", $headers);
740 +
		// Unfold headers (replace [CRLF] 1*( SP | HT ) with SP) as per RFC 2616 (section 2.2)
741 +
		$headers = preg_replace('/\n[ \t]/', ' ', $headers);
742 +
		$headers = explode("\n", $headers);
743 +
		preg_match('#^HTTP/(1\.\d)[ \t]+(\d+)#i', array_shift($headers), $matches);
744 +
		if (empty($matches)) {
745 +
			throw new Exception('Response could not be parsed', 'noversion', $headers);
746 +
		}
747 +
		$return->protocol_version = (float) $matches[1];
748 +
		$return->status_code      = (int) $matches[2];
749 +
		if ($return->status_code >= 200 && $return->status_code < 300) {
750 +
			$return->success = true;
751 +
		}
752 +
753 +
		foreach ($headers as $header) {
754 +
			list($key, $value) = explode(':', $header, 2);
755 +
			$value             = trim($value);
756 +
			preg_replace('#(\s+)#i', ' ', $value);
757 +
			$return->headers[$key] = $value;
758 +
		}
759 +
		if (isset($return->headers['transfer-encoding'])) {
760 +
			$return->body = self::decode_chunked($return->body);
761 +
			unset($return->headers['transfer-encoding']);
762 +
		}
763 +
		if (isset($return->headers['content-encoding'])) {
764 +
			$return->body = self::decompress($return->body);
765 +
		}
766 +
767 +
		//fsockopen and cURL compatibility
768 +
		if (isset($return->headers['connection'])) {
769 +
			unset($return->headers['connection']);
770 +
		}
771 +
772 +
		$options['hooks']->dispatch('requests.before_redirect_check', [&$return, $req_headers, $req_data, $options]);
773 +
774 +
		if ($return->is_redirect() && $options['follow_redirects'] === true) {
775 +
			if (isset($return->headers['location']) && $options['redirected'] < $options['redirects']) {
776 +
				if ($return->status_code === 303) {
777 +
					$options['type'] = self::GET;
778 +
				}
779 +
				$options['redirected']++;
780 +
				$location = $return->headers['location'];
781 +
				if (strpos($location, 'http://') !== 0 && strpos($location, 'https://') !== 0) {
782 +
					// relative redirect, for compatibility make it absolute
783 +
					$location = Iri::absolutize($url, $location);
784 +
					$location = $location->uri;
785 +
				}
786 +
787 +
				$hook_args = [
788 +
					&$location,
789 +
					&$req_headers,
790 +
					&$req_data,
791 +
					&$options,
792 +
					$return,
793 +
				];
794 +
				$options['hooks']->dispatch('requests.before_redirect', $hook_args);
795 +
				$redirected            = self::request($location, $req_headers, $req_data, $options['type'], $options);
796 +
				$redirected->history[] = $return;
797 +
				return $redirected;
798 +
			}
799 +
			elseif ($options['redirected'] >= $options['redirects']) {
800 +
				throw new Exception('Too many redirects', 'toomanyredirects', $return);
801 +
			}
802 +
		}
803 +
804 +
		$return->redirects = $options['redirected'];
805 +
806 +
		$options['hooks']->dispatch('requests.after_request', [&$return, $req_headers, $req_data, $options]);
807 +
		return $return;
808 +
	}
809 +
810 +
	/**
811 +
	 * Callback for `transport.internal.parse_response`
812 +
	 *
813 +
	 * Internal use only. Converts a raw HTTP response to a \WpOrg\Requests\Response
814 +
	 * while still executing a multiple request.
815 +
	 *
816 +
	 * @param string $response Full response text including headers and body (will be overwritten with Response instance)
817 +
	 * @param array $request Request data as passed into {@see \WpOrg\Requests\Requests::request_multiple()}
818 +
	 * @return void `$response` is either set to a \WpOrg\Requests\Response instance, or a \WpOrg\Requests\Exception object
819 +
	 */
820 +
	public static function parse_multiple(&$response, $request) {
821 +
		try {
822 +
			$url      = $request['url'];
823 +
			$headers  = $request['headers'];
824 +
			$data     = $request['data'];
825 +
			$options  = $request['options'];
826 +
			$response = self::parse_response($response, $url, $headers, $data, $options);
827 +
		}
828 +
		catch (Exception $e) {
829 +
			$response = $e;
830 +
		}
831 +
	}
832 +
833 +
	/**
834 +
	 * Decoded a chunked body as per RFC 2616
835 +
	 *
836 +
	 * @link https://tools.ietf.org/html/rfc2616#section-3.6.1
837 +
	 * @param string $data Chunked body
838 +
	 * @return string Decoded body
839 +
	 */
840 +
	protected static function decode_chunked($data) {
841 +
		if (!preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', trim($data))) {
842 +
			return $data;
843 +
		}
844 +
845 +
		$decoded = '';
846 +
		$encoded = $data;
847 +
848 +
		while (true) {
849 +
			$is_chunked = (bool) preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', $encoded, $matches);
850 +
			if (!$is_chunked) {
851 +
				// Looks like it's not chunked after all
852 +
				return $data;
853 +
			}
854 +
855 +
			$length = hexdec(trim($matches[1]));
856 +
			if ($length === 0) {
857 +
				// Ignore trailer headers
858 +
				return $decoded;
859 +
			}
860 +
861 +
			$chunk_length = strlen($matches[0]);
862 +
			$decoded     .= substr($encoded, $chunk_length, $length);
863 +
			$encoded      = substr($encoded, $chunk_length + $length + 2);
864 +
865 +
			if (trim($encoded) === '0' || empty($encoded)) {
866 +
				return $decoded;
867 +
			}
868 +
		}
869 +
870 +
		// We'll never actually get down here
871 +
		// @codeCoverageIgnoreStart
872 +
	}
873 +
	// @codeCoverageIgnoreEnd
874 +
875 +
	/**
876 +
	 * Convert a key => value array to a 'key: value' array for headers
877 +
	 *
878 +
	 * @param iterable $dictionary Dictionary of header values
879 +
	 * @return array List of headers
880 +
	 *
881 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not iterable.
882 +
	 */
883 +
	public static function flatten($dictionary) {
884 +
		if (InputValidator::is_iterable($dictionary) === false) {
885 +
			throw InvalidArgument::create(1, '$dictionary', 'iterable', gettype($dictionary));
886 +
		}
887 +
888 +
		$return = [];
889 +
		foreach ($dictionary as $key => $value) {
890 +
			$return[] = sprintf('%s: %s', $key, $value);
891 +
		}
892 +
		return $return;
893 +
	}
894 +
895 +
	/**
896 +
	 * Decompress an encoded body
897 +
	 *
898 +
	 * Implements gzip, compress and deflate. Guesses which it is by attempting
899 +
	 * to decode.
900 +
	 *
901 +
	 * @param string $data Compressed data in one of the above formats
902 +
	 * @return string Decompressed string
903 +
	 *
904 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string.
905 +
	 */
906 +
	public static function decompress($data) {
907 +
		if (is_string($data) === false) {
908 +
			throw InvalidArgument::create(1, '$data', 'string', gettype($data));
909 +
		}
910 +
911 +
		if (trim($data) === '') {
912 +
			// Empty body does not need further processing.
913 +
			return $data;
914 +
		}
915 +
916 +
		$marker = substr($data, 0, 2);
917 +
		if (!isset(self::$magic_compression_headers[$marker])) {
918 +
			// Not actually compressed. Probably cURL ruining this for us.
919 +
			return $data;
920 +
		}
921 +
922 +
		if (function_exists('gzdecode')) {
923 +
			$decoded = @gzdecode($data);
924 +
			if ($decoded !== false) {
925 +
				return $decoded;
926 +
			}
927 +
		}
928 +
929 +
		if (function_exists('gzinflate')) {
930 +
			$decoded = @gzinflate($data);
931 +
			if ($decoded !== false) {
932 +
				return $decoded;
933 +
			}
934 +
		}
935 +
936 +
		$decoded = self::compatible_gzinflate($data);
937 +
		if ($decoded !== false) {
938 +
			return $decoded;
939 +
		}
940 +
941 +
		if (function_exists('gzuncompress')) {
942 +
			$decoded = @gzuncompress($data);
943 +
			if ($decoded !== false) {
944 +
				return $decoded;
945 +
			}
946 +
		}
947 +
948 +
		return $data;
949 +
	}
950 +
951 +
	/**
952 +
	 * Decompression of deflated string while staying compatible with the majority of servers.
953 +
	 *
954 +
	 * Certain Servers will return deflated data with headers which PHP's gzinflate()
955 +
	 * function cannot handle out of the box. The following function has been created from
956 +
	 * various snippets on the gzinflate() PHP documentation.
957 +
	 *
958 +
	 * Warning: Magic numbers within. Due to the potential different formats that the compressed
959 +
	 * data may be returned in, some "magic offsets" are needed to ensure proper decompression
960 +
	 * takes place. For a simple progmatic way to determine the magic offset in use, see:
961 +
	 * https://core.trac.wordpress.org/ticket/18273
962 +
	 *
963 +
	 * @since 1.6.0
964 +
	 * @link https://core.trac.wordpress.org/ticket/18273
965 +
	 * @link https://www.php.net/gzinflate#70875
966 +
	 * @link https://www.php.net/gzinflate#77336
967 +
	 *
968 +
	 * @param string $gz_data String to decompress.
969 +
	 * @return string|bool False on failure.
970 +
	 *
971 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string.
972 +
	 */
973 +
	public static function compatible_gzinflate($gz_data) {
974 +
		if (is_string($gz_data) === false) {
975 +
			throw InvalidArgument::create(1, '$gz_data', 'string', gettype($gz_data));
976 +
		}
977 +
978 +
		if (trim($gz_data) === '') {
979 +
			return false;
980 +
		}
981 +
982 +
		// Compressed data might contain a full zlib header, if so strip it for
983 +
		// gzinflate()
984 +
		if (substr($gz_data, 0, 3) === "\x1f\x8b\x08") {
985 +
			$i   = 10;
986 +
			$flg = ord(substr($gz_data, 3, 1));
987 +
			if ($flg > 0) {
988 +
				if ($flg & 4) {
989 +
					list($xlen) = unpack('v', substr($gz_data, $i, 2));
990 +
					$i         += 2 + $xlen;
991 +
				}
992 +
				if ($flg & 8) {
993 +
					$i = strpos($gz_data, "\0", $i) + 1;
994 +
				}
995 +
				if ($flg & 16) {
996 +
					$i = strpos($gz_data, "\0", $i) + 1;
997 +
				}
998 +
				if ($flg & 2) {
999 +
					$i += 2;
1000 +
				}
1001 +
			}
1002 +
			$decompressed = self::compatible_gzinflate(substr($gz_data, $i));
1003 +
			if ($decompressed !== false) {
1004 +
				return $decompressed;
1005 +
			}
1006 +
		}
1007 +
1008 +
		// If the data is Huffman Encoded, we must first strip the leading 2
1009 +
		// byte Huffman marker for gzinflate()
1010 +
		// The response is Huffman coded by many compressors such as
1011 +
		// java.util.zip.Deflater, Ruby's Zlib::Deflate, and .NET's
1012 +
		// System.IO.Compression.DeflateStream.
1013 +
		//
1014 +
		// See https://decompres.blogspot.com/ for a quick explanation of this
1015 +
		// data type
1016 +
		$huffman_encoded = false;
1017 +
1018 +
		// low nibble of first byte should be 0x08
1019 +
		list(, $first_nibble) = unpack('h', $gz_data);
1020 +
1021 +
		// First 2 bytes should be divisible by 0x1F
1022 +
		list(, $first_two_bytes) = unpack('n', $gz_data);
1023 +
1024 +
		if ($first_nibble === 0x08 && ($first_two_bytes % 0x1F) === 0) {
1025 +
			$huffman_encoded = true;
1026 +
		}
1027 +
1028 +
		if ($huffman_encoded) {
1029 +
			$decompressed = @gzinflate(substr($gz_data, 2));
1030 +
			if ($decompressed !== false) {
1031 +
				return $decompressed;
1032 +
			}
1033 +
		}
1034 +
1035 +
		if (substr($gz_data, 0, 4) === "\x50\x4b\x03\x04") {
1036 +
			// ZIP file format header
1037 +
			// Offset 6: 2 bytes, General-purpose field
1038 +
			// Offset 26: 2 bytes, filename length
1039 +
			// Offset 28: 2 bytes, optional field length
1040 +
			// Offset 30: Filename field, followed by optional field, followed
1041 +
			// immediately by data
1042 +
			list(, $general_purpose_flag) = unpack('v', substr($gz_data, 6, 2));
1043 +
1044 +
			// If the file has been compressed on the fly, 0x08 bit is set of
1045 +
			// the general purpose field. We can use this to differentiate
1046 +
			// between a compressed document, and a ZIP file
1047 +
			$zip_compressed_on_the_fly = ((0x08 & $general_purpose_flag) === 0x08);
1048 +
1049 +
			if (!$zip_compressed_on_the_fly) {
1050 +
				// Don't attempt to decode a compressed zip file
1051 +
				return $gz_data;
1052 +
			}
1053 +
1054 +
			// Determine the first byte of data, based on the above ZIP header
1055 +
			// offsets:
1056 +
			$first_file_start = array_sum(unpack('v2', substr($gz_data, 26, 4)));
1057 +
			$decompressed     = @gzinflate(substr($gz_data, 30 + $first_file_start));
1058 +
			if ($decompressed !== false) {
1059 +
				return $decompressed;
1060 +
			}
1061 +
			return false;
1062 +
		}
1063 +
1064 +
		// Finally fall back to straight gzinflate
1065 +
		$decompressed = @gzinflate($gz_data);
1066 +
		if ($decompressed !== false) {
1067 +
			return $decompressed;
1068 +
		}
1069 +
1070 +
		// Fallback for all above failing, not expected, but included for
1071 +
		// debugging and preventing regressions and to track stats
1072 +
		$decompressed = @gzinflate(substr($gz_data, 2));
1073 +
		if ($decompressed !== false) {
1074 +
			return $decompressed;
1075 +
		}
1076 +
1077 +
		return false;
1078 +
	}
1079 +
}

@@ -0,0 +1,124 @@
Loading
1 +
<?php
2 +
/**
3 +
 * Case-insensitive dictionary, suitable for HTTP headers
4 +
 *
5 +
 * @package Requests
6 +
 */
7 +
8 +
namespace WpOrg\Requests\Response;
9 +
10 +
use WpOrg\Requests\Exception;
11 +
use WpOrg\Requests\Exception\InvalidArgument;
12 +
use WpOrg\Requests\Utility\CaseInsensitiveDictionary;
13 +
use WpOrg\Requests\Utility\FilteredIterator;
14 +
15 +
/**
16 +
 * Case-insensitive dictionary, suitable for HTTP headers
17 +
 *
18 +
 * @package Requests
19 +
 */
20 +
class Headers extends CaseInsensitiveDictionary {
21 +
	/**
22 +
	 * Get the given header
23 +
	 *
24 +
	 * Unlike {@see \WpOrg\Requests\Response\Headers::getValues()}, this returns a string. If there are
25 +
	 * multiple values, it concatenates them with a comma as per RFC2616.
26 +
	 *
27 +
	 * Avoid using this where commas may be used unquoted in values, such as
28 +
	 * Set-Cookie headers.
29 +
	 *
30 +
	 * @param string $offset
31 +
	 * @return string|null Header value
32 +
	 */
33 +
	public function offsetGet($offset) {
34 +
		if (is_string($offset)) {
35 +
			$offset = strtolower($offset);
36 +
		}
37 +
38 +
		if (!isset($this->data[$offset])) {
39 +
			return null;
40 +
		}
41 +
42 +
		return $this->flatten($this->data[$offset]);
43 +
	}
44 +
45 +
	/**
46 +
	 * Set the given item
47 +
	 *
48 +
	 * @param string $offset Item name
49 +
	 * @param string $value Item value
50 +
	 *
51 +
	 * @throws \WpOrg\Requests\Exception On attempting to use dictionary as list (`invalidset`)
52 +
	 */
53 +
	public function offsetSet($offset, $value) {
54 +
		if ($offset === null) {
55 +
			throw new Exception('Object is a dictionary, not a list', 'invalidset');
56 +
		}
57 +
58 +
		if (is_string($offset)) {
59 +
			$offset = strtolower($offset);
60 +
		}
61 +
62 +
		if (!isset($this->data[$offset])) {
63 +
			$this->data[$offset] = [];
64 +
		}
65 +
66 +
		$this->data[$offset][] = $value;
67 +
	}
68 +
69 +
	/**
70 +
	 * Get all values for a given header
71 +
	 *
72 +
	 * @param string $offset
73 +
	 * @return array|null Header values
74 +
	 *
75 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not valid as an array key.
76 +
	 */
77 +
	public function getValues($offset) {
78 +
		if (!is_string($offset) && !is_int($offset)) {
79 +
			throw InvalidArgument::create(1, '$offset', 'string|int', gettype($offset));
80 +
		}
81 +
82 +
		$offset = strtolower($offset);
83 +
		if (!isset($this->data[$offset])) {
84 +
			return null;
85 +
		}
86 +
87 +
		return $this->data[$offset];
88 +
	}
89 +
90 +
	/**
91 +
	 * Flattens a value into a string
92 +
	 *
93 +
	 * Converts an array into a string by imploding values with a comma, as per
94 +
	 * RFC2616's rules for folding headers.
95 +
	 *
96 +
	 * @param string|array $value Value to flatten
97 +
	 * @return string Flattened value
98 +
	 *
99 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string or an array.
100 +
	 */
101 +
	public function flatten($value) {
102 +
		if (is_string($value)) {
103 +
			return $value;
104 +
		}
105 +
106 +
		if (is_array($value)) {
107 +
			return implode(',', $value);
108 +
		}
109 +
110 +
		throw InvalidArgument::create(1, '$value', 'string|array', gettype($value));
111 +
	}
112 +
113 +
	/**
114 +
	 * Get an iterator for the data
115 +
	 *
116 +
	 * Converts the internally stored values to a comma-separated string if there is more
117 +
	 * than one value for a key.
118 +
	 *
119 +
	 * @return \ArrayIterator
120 +
	 */
121 +
	public function getIterator() {
122 +
		return new FilteredIterator($this->data, [$this, 'flatten']);
123 +
	}
124 +
}
0 125
imilarity index 51%
1 126
ename from library/Requests/Session.php
2 127
ename to src/Session.php

@@ -2,10 +2,17 @@
Loading
2 2
/**
3 3
 * IRI parser/serialiser/normaliser
4 4
 *
5 -
 * @package Requests
6 -
 * @subpackage Utilities
5 +
 * @package Requests\Utilities
7 6
 */
8 7
8 +
namespace WpOrg\Requests;
9 +
10 +
use WpOrg\Requests\Exception;
11 +
use WpOrg\Requests\Exception\InvalidArgument;
12 +
use WpOrg\Requests\Ipv6;
13 +
use WpOrg\Requests\Port;
14 +
use WpOrg\Requests\Utility\InputValidator;
15 +
9 16
/**
10 17
 * IRI parser/serialiser/normaliser
11 18
 *
@@ -38,16 +45,15 @@
Loading
38 45
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
39 46
 * POSSIBILITY OF SUCH DAMAGE.
40 47
 *
41 -
 * @package Requests
42 -
 * @subpackage Utilities
48 +
 * @package Requests\Utilities
43 49
 * @author Geoffrey Sneddon
44 50
 * @author Steve Minutillo
45 51
 * @copyright 2007-2009 Geoffrey Sneddon and Steve Minutillo
46 -
 * @license http://www.opensource.org/licenses/bsd-license.php
52 +
 * @license https://opensource.org/licenses/bsd-license.php
47 53
 * @link http://hg.gsnedders.com/iri/
48 54
 *
49 55
 * @property string $iri IRI we're working with
50 -
 * @property-read string $uri IRI in URI form, {@see to_uri}
56 +
 * @property-read string $uri IRI in URI form, {@see \WpOrg\Requests\IRI::to_uri()}
51 57
 * @property string $scheme Scheme part of the IRI
52 58
 * @property string $authority Authority part, formatted for a URI (userinfo + host + port)
53 59
 * @property string $iauthority Authority part of the IRI (userinfo + host + port)
@@ -63,7 +69,7 @@
Loading
63 69
 * @property string $fragment Fragment, formatted for a URI (after '#')
64 70
 * @property string $ifragment Fragment part of the IRI (after '#')
65 71
 */
66 -
class Requests_IRI {
72 +
class Iri {
67 73
	/**
68 74
	 * Scheme
69 75
	 *
@@ -123,19 +129,19 @@
Loading
123 129
	 */
124 130
	protected $normalization = array(
125 131
		'acap' => array(
126 -
			'port' => 674
132 +
			'port' => Port::ACAP,
127 133
		),
128 134
		'dict' => array(
129 -
			'port' => 2628
135 +
			'port' => Port::DICT,
130 136
		),
131 137
		'file' => array(
132 -
			'ihost' => 'localhost'
138 +
			'ihost' => 'localhost',
133 139
		),
134 140
		'http' => array(
135 -
			'port' => 80,
141 +
			'port' => Port::HTTP,
136 142
		),
137 143
		'https' => array(
138 -
			'port' => 443,
144 +
			'port' => Port::HTTPS,
139 145
		),
140 146
	);
141 147
@@ -240,9 +246,15 @@
Loading
240 246
	/**
241 247
	 * Create a new IRI object, from a specified string
242 248
	 *
243 -
	 * @param string|null $iri
249 +
	 * @param string|Stringable|null $iri
250 +
	 *
251 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $iri argument is not a string, Stringable or null.
244 252
	 */
245 253
	public function __construct($iri = null) {
254 +
		if ($iri !== null && InputValidator::is_string_or_stringable($iri) === false) {
255 +
			throw InvalidArgument::create(1, '$iri', 'string|Stringable|null', gettype($iri));
256 +
		}
257 +
246 258
		$this->set_iri($iri);
247 259
	}
248 260
@@ -251,13 +263,13 @@
Loading
251 263
	 *
252 264
	 * Returns false if $base is not absolute, otherwise an IRI.
253 265
	 *
254 -
	 * @param Requests_IRI|string $base (Absolute) Base IRI
255 -
	 * @param Requests_IRI|string $relative Relative IRI
256 -
	 * @return Requests_IRI|false
266 +
	 * @param \WpOrg\Requests\Iri|string $base (Absolute) Base IRI
267 +
	 * @param \WpOrg\Requests\Iri|string $relative Relative IRI
268 +
	 * @return \WpOrg\Requests\Iri|false
257 269
	 */
258 270
	public static function absolutize($base, $relative) {
259 -
		if (!($relative instanceof Requests_IRI)) {
260 -
			$relative = new Requests_IRI($relative);
271 +
		if (!($relative instanceof self)) {
272 +
			$relative = new self($relative);
261 273
		}
262 274
		if (!$relative->is_valid()) {
263 275
			return false;
@@ -266,8 +278,8 @@
Loading
266 278
			return clone $relative;
267 279
		}
268 280
269 -
		if (!($base instanceof Requests_IRI)) {
270 -
			$base = new Requests_IRI($base);
281 +
		if (!($base instanceof self)) {
282 +
			$base = new self($base);
271 283
		}
272 284
		if ($base->scheme === null || !$base->is_valid()) {
273 285
			return false;
@@ -279,7 +291,7 @@
Loading
279 291
				$target->scheme = $base->scheme;
280 292
			}
281 293
			else {
282 -
				$target = new Requests_IRI;
294 +
				$target = new self;
283 295
				$target->scheme = $base->scheme;
284 296
				$target->iuserinfo = $base->iuserinfo;
285 297
				$target->ihost = $base->ihost;
@@ -330,7 +342,7 @@
Loading
330 342
		$iri = trim($iri, "\x20\x09\x0A\x0C\x0D");
331 343
		$has_match = preg_match('/^((?P<scheme>[^:\/?#]+):)?(\/\/(?P<authority>[^\/?#]*))?(?P<path>[^?#]*)(\?(?P<query>[^#]*))?(#(?P<fragment>.*))?$/', $iri, $match);
332 344
		if (!$has_match) {
333 -
			throw new Requests_Exception('Cannot parse supplied IRI', 'iri.cannot_parse', $iri);
345 +
			throw new Exception('Cannot parse supplied IRI', 'iri.cannot_parse', $iri);
334 346
		}
335 347
336 348
		if ($match[1] === '') {
@@ -413,18 +425,18 @@
Loading
413 425
	/**
414 426
	 * Replace invalid character with percent encoding
415 427
	 *
416 -
	 * @param string $string Input string
428 +
	 * @param string $text Input string
417 429
	 * @param string $extra_chars Valid characters not in iunreserved or
418 430
	 *                            iprivate (this is ASCII-only)
419 431
	 * @param bool $iprivate Allow iprivate
420 432
	 * @return string
421 433
	 */
422 -
	protected function replace_invalid_with_pct_encoding($string, $extra_chars, $iprivate = false) {
434 +
	protected function replace_invalid_with_pct_encoding($text, $extra_chars, $iprivate = false) {
423 435
		// Normalize as many pct-encoded sections as possible
424 -
		$string = preg_replace_callback('/(?:%[A-Fa-f0-9]{2})+/', array($this, 'remove_iunreserved_percent_encoded'), $string);
436 +
		$text = preg_replace_callback('/(?:%[A-Fa-f0-9]{2})+/', array($this, 'remove_iunreserved_percent_encoded'), $text);
425 437
426 438
		// Replace invalid percent characters
427 -
		$string = preg_replace('/%(?![A-Fa-f0-9]{2})/', '%25', $string);
439 +
		$text = preg_replace('/%(?![A-Fa-f0-9]{2})/', '%25', $text);
428 440
429 441
		// Add unreserved and % to $extra_chars (the latter is safe because all
430 442
		// pct-encoded sections are now valid).
@@ -432,9 +444,9 @@
Loading
432 444
433 445
		// Now replace any bytes that aren't allowed with their pct-encoded versions
434 446
		$position = 0;
435 -
		$strlen = strlen($string);
436 -
		while (($position += strspn($string, $extra_chars, $position)) < $strlen) {
437 -
			$value = ord($string[$position]);
447 +
		$strlen = strlen($text);
448 +
		while (($position += strspn($text, $extra_chars, $position)) < $strlen) {
449 +
			$value = ord($text[$position]);
438 450
439 451
			// Start position
440 452
			$start = $position;
@@ -471,7 +483,7 @@
Loading
471 483
			if ($remaining) {
472 484
				if ($position + $length <= $strlen) {
473 485
					for ($position++; $remaining; $position++) {
474 -
						$value = ord($string[$position]);
486 +
						$value = ord($text[$position]);
475 487
476 488
						// Check that the byte is valid, then add it to the character:
477 489
						if (($value & 0xC0) === 0x80) {
@@ -522,7 +534,7 @@
Loading
522 534
				}
523 535
524 536
				for ($j = $start; $j <= $position; $j++) {
525 -
					$string = substr_replace($string, sprintf('%%%02X', ord($string[$j])), $j, 1);
537 +
					$text = substr_replace($text, sprintf('%%%02X', ord($text[$j])), $j, 1);
526 538
					$j += 2;
527 539
					$position += 2;
528 540
					$strlen += 2;
@@ -530,7 +542,7 @@
Loading
530 542
			}
531 543
		}
532 544
533 -
		return $string;
545 +
		return $text;
534 546
	}
535 547
536 548
	/**
@@ -539,13 +551,13 @@
Loading
539 551
	 * Removes sequences of percent encoded bytes that represent UTF-8
540 552
	 * encoded characters in iunreserved
541 553
	 *
542 -
	 * @param array $match PCRE match
554 +
	 * @param array $regex_match PCRE match
543 555
	 * @return string Replacement
544 556
	 */
545 -
	protected function remove_iunreserved_percent_encoded($match) {
557 +
	protected function remove_iunreserved_percent_encoded($regex_match) {
546 558
		// As we just have valid percent encoded sequences we can just explode
547 559
		// and ignore the first member of the returned array (an empty string).
548 -
		$bytes = explode('%', $match[0]);
560 +
		$bytes = explode('%', $regex_match[0]);
549 561
550 562
		// Initialize the new string (this is what will be returned) and that
551 563
		// there are no bytes remaining in the current sequence (unsurprising
@@ -721,6 +733,9 @@
Loading
721 733
		if ($iri === null) {
722 734
			return true;
723 735
		}
736 +
737 +
		$iri = (string) $iri;
738 +
724 739
		if (isset($cache[$iri])) {
725 740
			list($this->scheme,
726 741
				 $this->iuserinfo,
@@ -733,7 +748,7 @@
Loading
733 748
			return $return;
734 749
		}
735 750
736 -
		$parsed = $this->parse_iri((string) $iri);
751 +
		$parsed = $this->parse_iri($iri);
737 752
738 753
		$return = $this->set_scheme($parsed['scheme'])
739 754
			&& $this->set_authority($parsed['authority'])
@@ -863,8 +878,8 @@
Loading
863 878
			return true;
864 879
		}
865 880
		if (substr($ihost, 0, 1) === '[' && substr($ihost, -1) === ']') {
866 -
			if (Requests_IPv6::check_ipv6(substr($ihost, 1, -1))) {
867 -
				$this->ihost = '[' . Requests_IPv6::compress(substr($ihost, 1, -1)) . ']';
881 +
			if (Ipv6::check_ipv6(substr($ihost, 1, -1))) {
882 +
				$this->ihost = '[' . Ipv6::compress(substr($ihost, 1, -1)) . ']';
868 883
			}
869 884
			else {
870 885
				$this->ihost = null;
@@ -985,11 +1000,11 @@
Loading
985 1000
	/**
986 1001
	 * Convert an IRI to a URI (or parts thereof)
987 1002
	 *
988 -
	 * @param string|bool IRI to convert (or false from {@see get_iri})
1003 +
	 * @param string|bool $iri IRI to convert (or false from {@see \WpOrg\Requests\IRI::get_iri()})
989 1004
	 * @return string|false URI if IRI is valid, false otherwise.
990 1005
	 */
991 -
	protected function to_uri($string) {
992 -
		if (!is_string($string)) {
1006 +
	protected function to_uri($iri) {
1007 +
		if (!is_string($iri)) {
993 1008
			return false;
994 1009
		}
995 1010
@@ -999,14 +1014,14 @@
Loading
999 1014
		}
1000 1015
1001 1016
		$position = 0;
1002 -
		$strlen = strlen($string);
1003 -
		while (($position += strcspn($string, $non_ascii, $position)) < $strlen) {
1004 -
			$string = substr_replace($string, sprintf('%%%02X', ord($string[$position])), $position, 1);
1017 +
		$strlen = strlen($iri);
1018 +
		while (($position += strcspn($iri, $non_ascii, $position)) < $strlen) {
1019 +
			$iri = substr_replace($iri, sprintf('%%%02X', ord($iri[$position])), $position, 1);
1005 1020
			$position += 3;
1006 1021
			$strlen += 2;
1007 1022
		}
1008 1023
1009 -
		return $string;
1024 +
		return $iri;
1010 1025
	}
1011 1026
1012 1027
	/**

@@ -0,0 +1,41 @@
Loading
1 +
<?php
2 +
3 +
namespace WpOrg\Requests\Exception;
4 +
5 +
use InvalidArgumentException;
6 +
7 +
/**
8 +
 * Exception for an invalid argument passed.
9 +
 *
10 +
 * @package Requests\Exceptions
11 +
 * @since   2.0.0
12 +
 */
13 +
final class InvalidArgument extends InvalidArgumentException {
14 +
15 +
	/**
16 +
	 * Create a new invalid argument exception with a standardized text.
17 +
	 *
18 +
	 * @param int    $position The argument position in the function signature. 1-based.
19 +
	 * @param string $name     The argument name in the function signature.
20 +
	 * @param string $expected The argument type expected as a string.
21 +
	 * @param string $received The actual argument type received.
22 +
	 *
23 +
	 * @return \WpOrg\Requests\Exception\InvalidArgument
24 +
	 */
25 +
	public static function create($position, $name, $expected, $received) {
26 +
		// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
27 +
		$stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
28 +
29 +
		return new self(
30 +
			sprintf(
31 +
				'%s::%s(): Argument #%d (%s) must be of type %s, %s given',
32 +
				$stack[1]['class'],
33 +
				$stack[1]['function'],
34 +
				$position,
35 +
				$name,
36 +
				$expected,
37 +
				$received
38 +
			)
39 +
		);
40 +
	}
41 +
}

@@ -0,0 +1,186 @@
Loading
1 +
<?php
2 +
/**
3 +
 * Cookie holder object
4 +
 *
5 +
 * @package Requests\Cookies
6 +
 */
7 +
8 +
namespace WpOrg\Requests\Cookie;
9 +
10 +
use ArrayAccess;
11 +
use ArrayIterator;
12 +
use IteratorAggregate;
13 +
use ReturnTypeWillChange;
14 +
use WpOrg\Requests\Cookie;
15 +
use WpOrg\Requests\Exception;
16 +
use WpOrg\Requests\Exception\InvalidArgument;
17 +
use WpOrg\Requests\HookManager;
18 +
use WpOrg\Requests\Iri;
19 +
use WpOrg\Requests\Response;
20 +
21 +
/**
22 +
 * Cookie holder object
23 +
 *
24 +
 * @package Requests\Cookies
25 +
 */
26 +
class Jar implements ArrayAccess, IteratorAggregate {
27 +
	/**
28 +
	 * Actual item data
29 +
	 *
30 +
	 * @var array
31 +
	 */
32 +
	protected $cookies = [];
33 +
34 +
	/**
35 +
	 * Create a new jar
36 +
	 *
37 +
	 * @param array $cookies Existing cookie values
38 +
	 *
39 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not an array.
40 +
	 */
41 +
	public function __construct($cookies = []) {
42 +
		if (is_array($cookies) === false) {
43 +
			throw InvalidArgument::create(1, '$cookies', 'array', gettype($cookies));
44 +
		}
45 +
46 +
		$this->cookies = $cookies;
47 +
	}
48 +
49 +
	/**
50 +
	 * Normalise cookie data into a \WpOrg\Requests\Cookie
51 +
	 *
52 +
	 * @param string|\WpOrg\Requests\Cookie $cookie
53 +
	 * @return \WpOrg\Requests\Cookie
54 +
	 */
55 +
	public function normalize_cookie($cookie, $key = '') {
56 +
		if ($cookie instanceof Cookie) {
57 +
			return $cookie;
58 +
		}
59 +
60 +
		return Cookie::parse($cookie, $key);
61 +
	}
62 +
63 +
	/**
64 +
	 * Check if the given item exists
65 +
	 *
66 +
	 * @param string $offset Item key
67 +
	 * @return boolean Does the item exist?
68 +
	 */
69 +
	#[ReturnTypeWillChange]
70 +
	public function offsetExists($offset) {
71 +
		return isset($this->cookies[$offset]);
72 +
	}
73 +
74 +
	/**
75 +
	 * Get the value for the item
76 +
	 *
77 +
	 * @param string $offset Item key
78 +
	 * @return string|null Item value (null if offsetExists is false)
79 +
	 */
80 +
	#[ReturnTypeWillChange]
81 +
	public function offsetGet($offset) {
82 +
		if (!isset($this->cookies[$offset])) {
83 +
			return null;
84 +
		}
85 +
86 +
		return $this->cookies[$offset];
87 +
	}
88 +
89 +
	/**
90 +
	 * Set the given item
91 +
	 *
92 +
	 * @param string $offset Item name
93 +
	 * @param string $value Item value
94 +
	 *
95 +
	 * @throws \WpOrg\Requests\Exception On attempting to use dictionary as list (`invalidset`)
96 +
	 */
97 +
	#[ReturnTypeWillChange]
98 +
	public function offsetSet($offset, $value) {
99 +
		if ($offset === null) {
100 +
			throw new Exception('Object is a dictionary, not a list', 'invalidset');
101 +
		}
102 +
103 +
		$this->cookies[$offset] = $value;
104 +
	}
105 +
106 +
	/**
107 +
	 * Unset the given header
108 +
	 *
109 +
	 * @param string $offset
110 +
	 */
111 +
	#[ReturnTypeWillChange]
112 +
	public function offsetUnset($offset) {
113 +
		unset($this->cookies[$offset]);
114 +
	}
115 +
116 +
	/**
117 +
	 * Get an iterator for the data
118 +
	 *
119 +
	 * @return \ArrayIterator
120 +
	 */
121 +
	#[ReturnTypeWillChange]
122 +
	public function getIterator() {
123 +
		return new ArrayIterator($this->cookies);
124 +
	}
125 +
126 +
	/**
127 +
	 * Register the cookie handler with the request's hooking system
128 +
	 *
129 +
	 * @param \WpOrg\Requests\HookManager $hooks Hooking system
130 +
	 */
131 +
	public function register(HookManager $hooks) {
132 +
		$hooks->register('requests.before_request', [$this, 'before_request']);
133 +
		$hooks->register('requests.before_redirect_check', [$this, 'before_redirect_check']);
134 +
	}
135 +
136 +
	/**
137 +
	 * Add Cookie header to a request if we have any
138 +
	 *
139 +
	 * As per RFC 6265, cookies are separated by '; '
140 +
	 *
141 +
	 * @param string $url
142 +
	 * @param array $headers
143 +
	 * @param array $data
144 +
	 * @param string $type
145 +
	 * @param array $options
146 +
	 */
147 +
	public function before_request($url, &$headers, &$data, &$type, &$options) {
148 +
		if (!$url instanceof Iri) {
149 +
			$url = new Iri($url);
150 +
		}
151 +
152 +
		if (!empty($this->cookies)) {
153 +
			$cookies = [];
154 +
			foreach ($this->cookies as $key => $cookie) {
155 +
				$cookie = $this->normalize_cookie($cookie, $key);
156 +
157 +
				// Skip expired cookies
158 +
				if ($cookie->is_expired()) {
159 +
					continue;
160 +
				}
161 +
162 +
				if ($cookie->domain_matches($url->host)) {
163 +
					$cookies[] = $cookie->format_for_header();
164 +
				}
165 +
			}
166 +
167 +
			$headers['Cookie'] = implode('; ', $cookies);
168 +
		}
169 +
	}
170 +
171 +
	/**
172 +
	 * Parse all cookies from a response and attach them to the response
173 +
	 *
174 +
	 * @param \WpOrg\Requests\Response $response
175 +
	 */
176 +
	public function before_redirect_check(Response $response) {
177 +
		$url = $response->url;
178 +
		if (!$url instanceof Iri) {
179 +
			$url = new Iri($url);
180 +
		}
181 +
182 +
		$cookies           = Cookie::parse_from_headers($response->headers, $url);
183 +
		$this->cookies     = array_merge($this->cookies, $cookies);
184 +
		$response->cookies = $this;
185 +
	}
186 +
}
0 187
imilarity index 80%
1 188
ename from library/Requests/Exception.php
2 189
ename to src/Exception.php

@@ -2,20 +2,23 @@
Loading
2 2
/**
3 3
 * Class to validate and to work with IPv6 addresses
4 4
 *
5 -
 * @package Requests
6 -
 * @subpackage Utilities
5 +
 * @package Requests\Utilities
7 6
 */
8 7
8 +
namespace WpOrg\Requests;
9 +
10 +
use WpOrg\Requests\Exception\InvalidArgument;
11 +
use WpOrg\Requests\Utility\InputValidator;
12 +
9 13
/**
10 14
 * Class to validate and to work with IPv6 addresses
11 15
 *
12 16
 * This was originally based on the PEAR class of the same name, but has been
13 17
 * entirely rewritten.
14 18
 *
15 -
 * @package Requests
16 -
 * @subpackage Utilities
19 +
 * @package Requests\Utilities
17 20
 */
18 -
class Requests_IPv6 {
21 +
final class Ipv6 {
19 22
	/**
20 23
	 * Uncompresses an IPv6 address
21 24
	 *
@@ -30,11 +33,20 @@
Loading
30 33
	 * @author elfrink at introweb dot nl
31 34
	 * @author Josh Peck <jmp at joshpeck dot org>
32 35
	 * @copyright 2003-2005 The PHP Group
33 -
	 * @license http://www.opensource.org/licenses/bsd-license.php
34 -
	 * @param string $ip An IPv6 address
36 +
	 * @license https://opensource.org/licenses/bsd-license.php
37 +
	 *
38 +
	 * @param string|Stringable $ip An IPv6 address
35 39
	 * @return string The uncompressed IPv6 address
40 +
	 *
41 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string or a stringable object.
36 42
	 */
37 43
	public static function uncompress($ip) {
44 +
		if (InputValidator::is_string_or_stringable($ip) === false) {
45 +
			throw InvalidArgument::create(1, '$ip', 'string|Stringable', gettype($ip));
46 +
		}
47 +
48 +
		$ip = (string) $ip;
49 +
38 50
		if (substr_count($ip, '::') !== 1) {
39 51
			return $ip;
40 52
		}
@@ -78,12 +90,14 @@
Loading
78 90
	 * Example:  FF01:0:0:0:0:0:0:101   ->  FF01::101
79 91
	 *           0:0:0:0:0:0:0:1        ->  ::1
80 92
	 *
81 -
	 * @see uncompress()
93 +
	 * @see \WpOrg\Requests\IPv6::uncompress()
94 +
	 *
82 95
	 * @param string $ip An IPv6 address
83 96
	 * @return string The compressed IPv6 address
84 97
	 */
85 98
	public static function compress($ip) {
86 -
		// Prepare the IP to be compressed
99 +
		// Prepare the IP to be compressed.
100 +
		// Note: Input validation is handled in the `uncompress()` method, which is the first call made in this method.
87 101
		$ip       = self::uncompress($ip);
88 102
		$ip_parts = self::split_v6_v4($ip);
89 103
@@ -124,15 +138,15 @@
Loading
124 138
	 * @param string $ip An IPv6 address
125 139
	 * @return string[] [0] contains the IPv6 represented part, and [1] the IPv4 represented part
126 140
	 */
127 -
	protected static function split_v6_v4($ip) {
141 +
	private static function split_v6_v4($ip) {
128 142
		if (strpos($ip, '.') !== false) {
129 143
			$pos       = strrpos($ip, ':');
130 144
			$ipv6_part = substr($ip, 0, $pos);
131 145
			$ipv4_part = substr($ip, $pos + 1);
132 -
			return array($ipv6_part, $ipv4_part);
146 +
			return [$ipv6_part, $ipv4_part];
133 147
		}
134 148
		else {
135 -
			return array($ip, '');
149 +
			return [$ip, ''];
136 150
		}
137 151
	}
138 152
@@ -145,6 +159,7 @@
Loading
145 159
	 * @return bool true if $ip is a valid IPv6 address
146 160
	 */
147 161
	public static function check_ipv6($ip) {
162 +
		// Note: Input validation is handled in the `uncompress()` method, which is the first call made in this method.
148 163
		$ip                = self::uncompress($ip);
149 164
		list($ipv6, $ipv4) = self::split_v6_v4($ip);
150 165
		$ipv6              = explode(':', $ipv6);
151 166
imilarity index 91%
152 167
ename from library/Requests/IRI.php
153 168
ename to src/Iri.php

@@ -1,28 +1,45 @@
Loading
1 1
<?php
2 2
3 +
namespace WpOrg\Requests;
4 +
5 +
use WpOrg\Requests\Exception;
6 +
use WpOrg\Requests\Exception\InvalidArgument;
7 +
use WpOrg\Requests\Utility\InputValidator;
8 +
3 9
/**
4 10
 * IDNA URL encoder
5 11
 *
6 12
 * Note: Not fully compliant, as nameprep does nothing yet.
7 13
 *
8 -
 * @package Requests
9 -
 * @subpackage Utilities
10 -
 * @see https://tools.ietf.org/html/rfc3490 IDNA specification
11 -
 * @see https://tools.ietf.org/html/rfc3492 Punycode/Bootstrap specification
14 +
 * @package Requests\Utilities
15 +
 *
16 +
 * @link https://tools.ietf.org/html/rfc3490 IDNA specification
17 +
 * @link https://tools.ietf.org/html/rfc3492 Punycode/Bootstrap specification
12 18
 */
13 -
class Requests_IDNAEncoder {
19 +
class IdnaEncoder {
14 20
	/**
15 21
	 * ACE prefix used for IDNA
16 22
	 *
17 -
	 * @see https://tools.ietf.org/html/rfc3490#section-5
23 +
	 * @link https://tools.ietf.org/html/rfc3490#section-5
18 24
	 * @var string
19 25
	 */
20 26
	const ACE_PREFIX = 'xn--';
21 27
28 +
	/**
29 +
	 * Maximum length of a IDNA URL in ASCII.
30 +
	 *
31 +
	 * @see \WpOrg\Requests\IdnaEncoder::to_ascii()
32 +
	 *
33 +
	 * @since 2.0.0
34 +
	 *
35 +
	 * @var int
36 +
	 */
37 +
	const MAX_LENGTH = 64;
38 +
22 39
	/**#@+
23 40
	 * Bootstrap constant for Punycode
24 41
	 *
25 -
	 * @see https://tools.ietf.org/html/rfc3492#section-5
42 +
	 * @link https://tools.ietf.org/html/rfc3492#section-5
26 43
	 * @var int
27 44
	 */
28 45
	const BOOTSTRAP_BASE         = 36;
@@ -37,11 +54,16 @@
Loading
37 54
	/**
38 55
	 * Encode a hostname using Punycode
39 56
	 *
40 -
	 * @param string $string Hostname
57 +
	 * @param string|Stringable $hostname Hostname
41 58
	 * @return string Punycode-encoded hostname
59 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string or a stringable object.
42 60
	 */
43 -
	public static function encode($string) {
44 -
		$parts = explode('.', $string);
61 +
	public static function encode($hostname) {
62 +
		if (InputValidator::is_string_or_stringable($hostname) === false) {
63 +
			throw InvalidArgument::create(1, '$hostname', 'string|Stringable', gettype($hostname));
64 +
		}
65 +
66 +
		$parts = explode('.', $hostname);
45 67
		foreach ($parts as &$part) {
46 68
			$part = self::to_ascii($part);
47 69
		}
@@ -49,94 +71,101 @@
Loading
49 71
	}
50 72
51 73
	/**
52 -
	 * Convert a UTF-8 string to an ASCII string using Punycode
53 -
	 *
54 -
	 * @throws Requests_Exception Provided string longer than 64 ASCII characters (`idna.provided_too_long`)
55 -
	 * @throws Requests_Exception Prepared string longer than 64 ASCII characters (`idna.prepared_too_long`)
56 -
	 * @throws Requests_Exception Provided string already begins with xn-- (`idna.provided_is_prefixed`)
57 -
	 * @throws Requests_Exception Encoded string longer than 64 ASCII characters (`idna.encoded_too_long`)
74 +
	 * Convert a UTF-8 text string to an ASCII string using Punycode
58 75
	 *
59 -
	 * @param string $string ASCII or UTF-8 string (max length 64 characters)
76 +
	 * @param string $text ASCII or UTF-8 string (max length 64 characters)
60 77
	 * @return string ASCII string
78 +
	 *
79 +
	 * @throws \WpOrg\Requests\Exception Provided string longer than 64 ASCII characters (`idna.provided_too_long`)
80 +
	 * @throws \WpOrg\Requests\Exception Prepared string longer than 64 ASCII characters (`idna.prepared_too_long`)
81 +
	 * @throws \WpOrg\Requests\Exception Provided string already begins with xn-- (`idna.provided_is_prefixed`)
82 +
	 * @throws \WpOrg\Requests\Exception Encoded string longer than 64 ASCII characters (`idna.encoded_too_long`)
61 83
	 */
62 -
	public static function to_ascii($string) {
63 -
		// Step 1: Check if the string is already ASCII
64 -
		if (self::is_ascii($string)) {
84 +
	public static function to_ascii($text) {
85 +
		// Step 1: Check if the text is already ASCII
86 +
		if (self::is_ascii($text)) {
65 87
			// Skip to step 7
66 -
			if (strlen($string) < 64) {
67 -
				return $string;
88 +
			if (strlen($text) < self::MAX_LENGTH) {
89 +
				return $text;
68 90
			}
69 91
70 -
			throw new Requests_Exception('Provided string is too long', 'idna.provided_too_long', $string);
92 +
			throw new Exception('Provided string is too long', 'idna.provided_too_long', $text);
71 93
		}
72 94
73 95
		// Step 2: nameprep
74 -
		$string = self::nameprep($string);
96 +
		$text = self::nameprep($text);
75 97
76 98
		// Step 3: UseSTD3ASCIIRules is false, continue
77 99
		// Step 4: Check if it's ASCII now
78 -
		if (self::is_ascii($string)) {
100 +
		if (self::is_ascii($text)) {
79 101
			// Skip to step 7
80 -
			if (strlen($string) < 64) {
81 -
				return $string;
102 +
			/*
103 +
			 * As the `nameprep()` method returns the original string, this code will never be reached until
104 +
			 * that method is properly implemented.
105 +
			 */
106 +
			// @codeCoverageIgnoreStart
107 +
			if (strlen($text) < self::MAX_LENGTH) {
108 +
				return $text;
82 109
			}
83 110
84 -
			throw new Requests_Exception('Prepared string is too long', 'idna.prepared_too_long', $string);
111 +
			throw new Exception('Prepared string is too long', 'idna.prepared_too_long', $text);
112 +
			// @codeCoverageIgnoreEnd
85 113
		}
86 114
87 115
		// Step 5: Check ACE prefix
88 -
		if (strpos($string, self::ACE_PREFIX) === 0) {
89 -
			throw new Requests_Exception('Provided string begins with ACE prefix', 'idna.provided_is_prefixed', $string);
116 +
		if (strpos($text, self::ACE_PREFIX) === 0) {
117 +
			throw new Exception('Provided string begins with ACE prefix', 'idna.provided_is_prefixed', $text);
90 118
		}
91 119
92 120
		// Step 6: Encode with Punycode
93 -
		$string = self::punycode_encode($string);
121 +
		$text = self::punycode_encode($text);
94 122
95 123
		// Step 7: Prepend ACE prefix
96 -
		$string = self::ACE_PREFIX . $string;
124 +
		$text = self::ACE_PREFIX . $text;
97 125
98 126
		// Step 8: Check size
99 -
		if (strlen($string) < 64) {
100 -
			return $string;
127 +
		if (strlen($text) < self::MAX_LENGTH) {
128 +
			return $text;
101 129
		}
102 130
103 -
		throw new Requests_Exception('Encoded string is too long', 'idna.encoded_too_long', $string);
131 +
		throw new Exception('Encoded string is too long', 'idna.encoded_too_long', $text);
104 132
	}
105 133
106 134
	/**
107 -
	 * Check whether a given string contains only ASCII characters
135 +
	 * Check whether a given text string contains only ASCII characters
108 136
	 *
109 137
	 * @internal (Testing found regex was the fastest implementation)
110 138
	 *
111 -
	 * @param string $string
112 -
	 * @return bool Is the string ASCII-only?
139 +
	 * @param string $text
140 +
	 * @return bool Is the text string ASCII-only?
113 141
	 */
114 -
	protected static function is_ascii($string) {
115 -
		return (preg_match('/(?:[^\x00-\x7F])/', $string) !== 1);
142 +
	protected static function is_ascii($text) {
143 +
		return (preg_match('/(?:[^\x00-\x7F])/', $text) !== 1);
116 144
	}
117 145
118 146
	/**
119 -
	 * Prepare a string for use as an IDNA name
147 +
	 * Prepare a text string for use as an IDNA name
120 148
	 *
121 149
	 * @todo Implement this based on RFC 3491 and the newer 5891
122 -
	 * @param string $string
150 +
	 * @param string $text
123 151
	 * @return string Prepared string
124 152
	 */
125 -
	protected static function nameprep($string) {
126 -
		return $string;
153 +
	protected static function nameprep($text) {
154 +
		return $text;
127 155
	}
128 156
129 157
	/**
130 158
	 * Convert a UTF-8 string to a UCS-4 codepoint array
131 159
	 *
132 -
	 * Based on Requests_IRI::replace_invalid_with_pct_encoding()
160 +
	 * Based on \WpOrg\Requests\Iri::replace_invalid_with_pct_encoding()
133 161
	 *
134 -
	 * @throws Requests_Exception Invalid UTF-8 codepoint (`idna.invalidcodepoint`)
135 162
	 * @param string $input
136 163
	 * @return array Unicode code points
164 +
	 *
165 +
	 * @throws \WpOrg\Requests\Exception Invalid UTF-8 codepoint (`idna.invalidcodepoint`)
137 166
	 */
138 167
	protected static function utf8_to_codepoints($input) {
139 -
		$codepoints = array();
168 +
		$codepoints = [];
140 169
141 170
		// Get number of bytes
142 171
		$strlen = strlen($input);
@@ -171,19 +200,19 @@
Loading
171 200
			}
172 201
			// Invalid byte:
173 202
			else {
174 -
				throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $value);
203 +
				throw new Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $value);
175 204
			}
176 205
177 206
			if ($remaining > 0) {
178 207
				if ($position + $length > $strlen) {
179 -
					throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
208 +
					throw new Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
180 209
				}
181 210
				for ($position++; $remaining > 0; $position++) {
182 211
					$value = ord($input[$position]);
183 212
184 213
					// If it is invalid, count the sequence as invalid and reprocess the current byte:
185 214
					if (($value & 0xC0) !== 0x80) {
186 -
						throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
215 +
						throw new Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
187 216
					}
188 217
189 218
					--$remaining;
@@ -208,7 +237,7 @@
Loading
208 237
					|| $character > 0xEFFFD
209 238
				)
210 239
			) {
211 -
				throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
240 +
				throw new Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
212 241
			}
213 242
214 243
			$codepoints[] = $character;
@@ -221,10 +250,11 @@
Loading
221 250
	 * RFC3492-compliant encoder
222 251
	 *
223 252
	 * @internal Pseudo-code from Section 6.3 is commented with "#" next to relevant code
224 -
	 * @throws Requests_Exception On character outside of the domain (never happens with Punycode) (`idna.character_outside_domain`)
225 253
	 *
226 254
	 * @param string $input UTF-8 encoded string to encode
227 255
	 * @return string Punycode-encoded string
256 +
	 *
257 +
	 * @throws \WpOrg\Requests\Exception On character outside of the domain (never happens with Punycode) (`idna.character_outside_domain`)
228 258
	 */
229 259
	public static function punycode_encode($input) {
230 260
		$output = '';
@@ -239,7 +269,7 @@
Loading
239 269
		$b = 0; // see loop
240 270
		// copy them to the output in order
241 271
		$codepoints = self::utf8_to_codepoints($input);
242 -
		$extended   = array();
272 +
		$extended   = [];
243 273
244 274
		foreach ($codepoints as $char) {
245 275
			if ($char < 128) {
@@ -252,7 +282,7 @@
Loading
252 282
			// This never occurs for Punycode, so ignore in coverage
253 283
			// @codeCoverageIgnoreStart
254 284
			elseif ($char < $n) {
255 -
				throw new Requests_Exception('Invalid character', 'idna.character_outside_domain', $char);
285 +
				throw new Exception('Invalid character', 'idna.character_outside_domain', $char);
256 286
			}
257 287
			// @codeCoverageIgnoreEnd
258 288
			else {
@@ -332,17 +362,18 @@
Loading
332 362
	/**
333 363
	 * Convert a digit to its respective character
334 364
	 *
335 -
	 * @see https://tools.ietf.org/html/rfc3492#section-5
336 -
	 * @throws Requests_Exception On invalid digit (`idna.invalid_digit`)
365 +
	 * @link https://tools.ietf.org/html/rfc3492#section-5
337 366
	 *
338 367
	 * @param int $digit Digit in the range 0-35
339 368
	 * @return string Single character corresponding to digit
369 +
	 *
370 +
	 * @throws \WpOrg\Requests\Exception On invalid digit (`idna.invalid_digit`)
340 371
	 */
341 372
	protected static function digit_to_char($digit) {
342 373
		// @codeCoverageIgnoreStart
343 374
		// As far as I know, this never happens, but still good to be sure.
344 375
		if ($digit < 0 || $digit > 35) {
345 -
			throw new Requests_Exception(sprintf('Invalid digit %d', $digit), 'idna.invalid_digit', $digit);
376 +
			throw new Exception(sprintf('Invalid digit %d', $digit), 'idna.invalid_digit', $digit);
346 377
		}
347 378
		// @codeCoverageIgnoreEnd
348 379
		$digits = 'abcdefghijklmnopqrstuvwxyz0123456789';
@@ -352,7 +383,7 @@
Loading
352 383
	/**
353 384
	 * Adapt the bias
354 385
	 *
355 -
	 * @see https://tools.ietf.org/html/rfc3492#section-6.1
386 +
	 * @link https://tools.ietf.org/html/rfc3492#section-6.1
356 387
	 * @param int $delta
357 388
	 * @param int $numpoints
358 389
	 * @param bool $firsttime
359 390
imilarity index 81%
360 391
ename from library/Requests/IPv6.php
361 392
ename to src/Ipv6.php

@@ -2,15 +2,19 @@
Loading
2 2
/**
3 3
 * Exception for HTTP requests
4 4
 *
5 -
 * @package Requests
5 +
 * @package Requests\Exceptions
6 6
 */
7 7
8 +
namespace WpOrg\Requests;
9 +
10 +
use Exception as PHPException;
11 +
8 12
/**
9 13
 * Exception for HTTP requests
10 14
 *
11 -
 * @package Requests
15 +
 * @package Requests\Exceptions
12 16
 */
13 -
class Requests_Exception extends Exception {
17 +
class Exception extends PHPException {
14 18
	/**
15 19
	 * Type of exception
16 20
	 *
@@ -41,7 +45,7 @@
Loading
41 45
	}
42 46
43 47
	/**
44 -
	 * Like {@see getCode()}, but a string code.
48 +
	 * Like {@see \Exception::getCode()}, but a string code.
45 49
	 *
46 50
	 * @codeCoverageIgnore
47 51
	 * @return string

@@ -2,17 +2,27 @@
Loading
2 2
/**
3 3
 * fsockopen HTTP transport
4 4
 *
5 -
 * @package Requests
6 -
 * @subpackage Transport
5 +
 * @package Requests\Transport
7 6
 */
8 7
8 +
namespace WpOrg\Requests\Transport;
9 +
10 +
use WpOrg\Requests\Capability;
11 +
use WpOrg\Requests\Exception;
12 +
use WpOrg\Requests\Exception\InvalidArgument;
13 +
use WpOrg\Requests\Port;
14 +
use WpOrg\Requests\Requests;
15 +
use WpOrg\Requests\Ssl;
16 +
use WpOrg\Requests\Transport;
17 +
use WpOrg\Requests\Utility\CaseInsensitiveDictionary;
18 +
use WpOrg\Requests\Utility\InputValidator;
19 +
9 20
/**
10 21
 * fsockopen HTTP transport
11 22
 *
12 -
 * @package Requests
13 -
 * @subpackage Transport
23 +
 * @package Requests\Transport
14 24
 */
15 -
class Requests_Transport_fsockopen implements Requests_Transport {
25 +
final class Fsockopen implements Transport {
16 26
	/**
17 27
	 * Second to microsecond conversion
18 28
	 *
@@ -30,7 +40,7 @@
Loading
30 40
	/**
31 41
	 * Stream metadata
32 42
	 *
33 -
	 * @var array Associative array of properties, see {@see https://secure.php.net/stream_get_meta_data}
43 +
	 * @var array Associative array of properties, see {@link https://www.php.net/stream_get_meta_data}
34 44
	 */
35 45
	public $info;
36 46
@@ -39,45 +49,69 @@
Loading
39 49
	 *
40 50
	 * @var int|bool Byte count, or false if no limit.
41 51
	 */
42 -
	protected $max_bytes = false;
52 +
	private $max_bytes = false;
43 53
44 -
	protected $connect_error = '';
54 +
	private $connect_error = '';
45 55
46 56
	/**
47 57
	 * Perform a request
48 58
	 *
49 -
	 * @throws Requests_Exception On failure to connect to socket (`fsockopenerror`)
50 -
	 * @throws Requests_Exception On socket timeout (`timeout`)
51 -
	 *
52 -
	 * @param string $url URL to request
59 +
	 * @param string|Stringable $url URL to request
53 60
	 * @param array $headers Associative array of request headers
54 61
	 * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
55 -
	 * @param array $options Request options, see {@see Requests::response()} for documentation
62 +
	 * @param array $options Request options, see {@see \WpOrg\Requests\Requests::response()} for documentation
56 63
	 * @return string Raw HTTP result
64 +
	 *
65 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string or Stringable.
66 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $headers argument is not an array.
67 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $data parameter is not an array or string.
68 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
69 +
	 * @throws \WpOrg\Requests\Exception       On failure to connect to socket (`fsockopenerror`)
70 +
	 * @throws \WpOrg\Requests\Exception       On socket timeout (`timeout`)
57 71
	 */
58 -
	public function request($url, $headers = array(), $data = array(), $options = array()) {
72 +
	public function request($url, $headers = [], $data = [], $options = []) {
73 +
		if (InputValidator::is_string_or_stringable($url) === false) {
74 +
			throw InvalidArgument::create(1, '$url', 'string|Stringable', gettype($url));
75 +
		}
76 +
77 +
		if (is_array($headers) === false) {
78 +
			throw InvalidArgument::create(2, '$headers', 'array', gettype($headers));
79 +
		}
80 +
81 +
		if (!is_array($data) && !is_string($data)) {
82 +
			if ($data === null) {
83 +
				$data = '';
84 +
			} else {
85 +
				throw InvalidArgument::create(3, '$data', 'array|string', gettype($data));
86 +
			}
87 +
		}
88 +
89 +
		if (is_array($options) === false) {
90 +
			throw InvalidArgument::create(4, '$options', 'array', gettype($options));
91 +
		}
92 +
59 93
		$options['hooks']->dispatch('fsockopen.before_request');
60 94
61 95
		$url_parts = parse_url($url);
62 96
		if (empty($url_parts)) {
63 -
			throw new Requests_Exception('Invalid URL.', 'invalidurl', $url);
97 +
			throw new Exception('Invalid URL.', 'invalidurl', $url);
64 98
		}
65 99
		$host                     = $url_parts['host'];
66 100
		$context                  = stream_context_create();
67 101
		$verifyname               = false;
68 -
		$case_insensitive_headers = new Requests_Utility_CaseInsensitiveDictionary($headers);
102 +
		$case_insensitive_headers = new CaseInsensitiveDictionary($headers);
69 103
70 104
		// HTTPS support
71 105
		if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') {
72 106
			$remote_socket = 'ssl://' . $host;
73 107
			if (!isset($url_parts['port'])) {
74 -
				$url_parts['port'] = 443;
108 +
				$url_parts['port'] = Port::HTTPS;
75 109
			}
76 110
77 -
			$context_options = array(
111 +
			$context_options = [
78 112
				'verify_peer'       => true,
79 113
				'capture_peer_cert' => true,
80 -
			);
114 +
			];
81 115
			$verifyname      = true;
82 116
83 117
			// SNI, if enabled (OpenSSL >=0.9.8j)
@@ -105,7 +139,7 @@
Loading
105 139
				$verifyname                          = false;
106 140
			}
107 141
108 -
			stream_context_set_option($context, array('ssl' => $context_options));
142 +
			stream_context_set_option($context, ['ssl' => $context_options]);
109 143
		}
110 144
		else {
111 145
			$remote_socket = 'tcp://' . $host;
@@ -114,30 +148,30 @@
Loading
114 148
		$this->max_bytes = $options['max_bytes'];
115 149
116 150
		if (!isset($url_parts['port'])) {
117 -
			$url_parts['port'] = 80;
151 +
			$url_parts['port'] = Port::HTTP;
118 152
		}
119 153
		$remote_socket .= ':' . $url_parts['port'];
120 154
121 155
		// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler
122 -
		set_error_handler(array($this, 'connect_error_handler'), E_WARNING | E_NOTICE);
156 +
		set_error_handler([$this, 'connect_error_handler'], E_WARNING | E_NOTICE);
123 157
124 -
		$options['hooks']->dispatch('fsockopen.remote_socket', array(&$remote_socket));
158 +
		$options['hooks']->dispatch('fsockopen.remote_socket', [&$remote_socket]);
125 159
126 160
		$socket = stream_socket_client($remote_socket, $errno, $errstr, ceil($options['connect_timeout']), STREAM_CLIENT_CONNECT, $context);
127 161
128 162
		restore_error_handler();
129 163
130 164
		if ($verifyname && !$this->verify_certificate_from_context($host, $context)) {
131 -
			throw new Requests_Exception('SSL certificate did not match the requested domain name', 'ssl.no_match');
165 +
			throw new Exception('SSL certificate did not match the requested domain name', 'ssl.no_match');
132 166
		}
133 167
134 168
		if (!$socket) {
135 169
			if ($errno === 0) {
136 170
				// Connection issue
137 -
				throw new Requests_Exception(rtrim($this->connect_error), 'fsockopen.connect_error');
171 +
				throw new Exception(rtrim($this->connect_error), 'fsockopen.connect_error');
138 172
			}
139 173
140 -
			throw new Requests_Exception($errstr, 'fsockopenerror', null, $errno);
174 +
			throw new Exception($errstr, 'fsockopenerror', null, $errno);
141 175
		}
142 176
143 177
		$data_format = $options['data_format'];
@@ -147,10 +181,10 @@
Loading
147 181
			$data = '';
148 182
		}
149 183
		else {
150 -
			$path = self::format_get($url_parts, array());
184 +
			$path = self::format_get($url_parts, []);
151 185
		}
152 186
153 -
		$options['hooks']->dispatch('fsockopen.remote_host_path', array(&$path, $url));
187 +
		$options['hooks']->dispatch('fsockopen.remote_host_path', [&$path, $url]);
154 188
155 189
		$request_body = '';
156 190
		$out          = sprintf("%s %s HTTP/%.1F\r\n", $options['type'], $path, $options['protocol_version']);
@@ -177,9 +211,10 @@
Loading
177 211
		}
178 212
179 213
		if (!isset($case_insensitive_headers['Host'])) {
180 -
			$out .= sprintf('Host: %s', $url_parts['host']);
214 +
			$out         .= sprintf('Host: %s', $url_parts['host']);
215 +
			$scheme_lower = strtolower($url_parts['scheme']);
181 216
182 -
			if ((strtolower($url_parts['scheme']) === 'http' && $url_parts['port'] !== 80) || (strtolower($url_parts['scheme']) === 'https' && $url_parts['port'] !== 443)) {
217 +
			if (($scheme_lower === 'http' && $url_parts['port'] !== Port::HTTP) || ($scheme_lower === 'https' && $url_parts['port'] !== Port::HTTPS)) {
183 218
				$out .= ':' . $url_parts['port'];
184 219
			}
185 220
			$out .= "\r\n";
@@ -200,7 +235,7 @@
Loading
200 235
			$out .= implode("\r\n", $headers) . "\r\n";
201 236
		}
202 237
203 -
		$options['hooks']->dispatch('fsockopen.after_headers', array(&$out));
238 +
		$options['hooks']->dispatch('fsockopen.after_headers', [&$out]);
204 239
205 240
		if (substr($out, -2) !== "\r\n") {
206 241
			$out .= "\r\n";
@@ -212,15 +247,15 @@
Loading
212 247
213 248
		$out .= "\r\n" . $request_body;
214 249
215 -
		$options['hooks']->dispatch('fsockopen.before_send', array(&$out));
250 +
		$options['hooks']->dispatch('fsockopen.before_send', [&$out]);
216 251
217 252
		fwrite($socket, $out);
218 -
		$options['hooks']->dispatch('fsockopen.after_send', array($out));
253 +
		$options['hooks']->dispatch('fsockopen.after_send', [$out]);
219 254
220 255
		if (!$options['blocking']) {
221 256
			fclose($socket);
222 257
			$fake_headers = '';
223 -
			$options['hooks']->dispatch('fsockopen.after_request', array(&$fake_headers));
258 +
			$options['hooks']->dispatch('fsockopen.after_request', [&$fake_headers]);
224 259
			return '';
225 260
		}
226 261
@@ -241,13 +276,18 @@
Loading
241 276
		$doingbody  = false;
242 277
		$download   = false;
243 278
		if ($options['filename']) {
244 -
			$download = fopen($options['filename'], 'wb');
279 +
			// phpcs:ignore WordPress.PHP.NoSilencedErrors -- Silenced the PHP native warning in favour of throwing an exception.
280 +
			$download = @fopen($options['filename'], 'wb');
281 +
			if ($download === false) {
282 +
				$error = error_get_last();
283 +
				throw new Exception($error['message'], 'fopen');
284 +
			}
245 285
		}
246 286
247 287
		while (!feof($socket)) {
248 288
			$this->info = stream_get_meta_data($socket);
249 289
			if ($this->info['timed_out']) {
250 -
				throw new Requests_Exception('fsocket timed out', 'timeout');
290 +
				throw new Exception('fsocket timed out', 'timeout');
251 291
			}
252 292
253 293
			$block = fread($socket, Requests::BUFFER_SIZE);
@@ -261,7 +301,7 @@
Loading
261 301
262 302
			// Are we in body mode now?
263 303
			if ($doingbody) {
264 -
				$options['hooks']->dispatch('request.progress', array($block, $size, $this->max_bytes));
304 +
				$options['hooks']->dispatch('request.progress', [$block, $size, $this->max_bytes]);
265 305
				$data_length = strlen($block);
266 306
				if ($this->max_bytes) {
267 307
					// Have we already hit a limit?
@@ -294,33 +334,49 @@
Loading
294 334
		}
295 335
		fclose($socket);
296 336
297 -
		$options['hooks']->dispatch('fsockopen.after_request', array(&$this->headers, &$this->info));
337 +
		$options['hooks']->dispatch('fsockopen.after_request', [&$this->headers, &$this->info]);
298 338
		return $this->headers;
299 339
	}
300 340
301 341
	/**
302 342
	 * Send multiple requests simultaneously
303 343
	 *
304 -
	 * @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see Requests_Transport::request}
305 -
	 * @param array $options Global options, see {@see Requests::response()} for documentation
306 -
	 * @return array Array of Requests_Response objects (may contain Requests_Exception or string responses as well)
344 +
	 * @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see \WpOrg\Requests\Transport::request()}
345 +
	 * @param array $options Global options, see {@see \WpOrg\Requests\Requests::response()} for documentation
346 +
	 * @return array Array of \WpOrg\Requests\Response objects (may contain \WpOrg\Requests\Exception or string responses as well)
347 +
	 *
348 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access.
349 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
307 350
	 */
308 351
	public function request_multiple($requests, $options) {
309 -
		$responses = array();
352 +
		// If you're not requesting, we can't get any responses ¯\_(ツ)_/¯
353 +
		if (empty($requests)) {
354 +
			return [];
355 +
		}
356 +
357 +
		if (InputValidator::has_array_access($requests) === false || InputValidator::is_iterable($requests) === false) {
358 +
			throw InvalidArgument::create(1, '$requests', 'array|ArrayAccess&Traversable', gettype($requests));
359 +
		}
360 +
361 +
		if (is_array($options) === false) {
362 +
			throw InvalidArgument::create(2, '$options', 'array', gettype($options));
363 +
		}
364 +
365 +
		$responses = [];
310 366
		$class     = get_class($this);
311 367
		foreach ($requests as $id => $request) {
312 368
			try {
313 369
				$handler        = new $class();
314 370
				$responses[$id] = $handler->request($request['url'], $request['headers'], $request['data'], $request['options']);
315 371
316 -
				$request['options']['hooks']->dispatch('transport.internal.parse_response', array(&$responses[$id], $request));
372 +
				$request['options']['hooks']->dispatch('transport.internal.parse_response', [&$responses[$id], $request]);
317 373
			}
318 -
			catch (Requests_Exception $e) {
374 +
			catch (Exception $e) {
319 375
				$responses[$id] = $e;
320 376
			}
321 377
322 378
			if (!is_string($responses[$id])) {
323 -
				$request['options']['hooks']->dispatch('multiple.request.complete', array(&$responses[$id], $id));
379 +
				$request['options']['hooks']->dispatch('multiple.request.complete', [&$responses[$id], $id]);
324 380
			}
325 381
		}
326 382
@@ -332,8 +388,8 @@
Loading
332 388
	 *
333 389
	 * @return string Accept-Encoding header value
334 390
	 */
335 -
	protected static function accept_encoding() {
336 -
		$type = array();
391 +
	private static function accept_encoding() {
392 +
		$type = [];
337 393
		if (function_exists('gzinflate')) {
338 394
			$type[] = 'deflate;q=1.0';
339 395
		}
@@ -351,10 +407,10 @@
Loading
351 407
	 * Format a URL given GET data
352 408
	 *
353 409
	 * @param array $url_parts
354 -
	 * @param array|object $data Data to build query using, see {@see https://secure.php.net/http_build_query}
410 +
	 * @param array|object $data Data to build query using, see {@link https://www.php.net/http_build_query}
355 411
	 * @return string URL with data
356 412
	 */
357 -
	protected static function format_get($url_parts, $data) {
413 +
	private static function format_get($url_parts, $data) {
358 414
		if (!empty($data)) {
359 415
			if (empty($url_parts['query'])) {
360 416
				$url_parts['query'] = '';
@@ -401,13 +457,14 @@
Loading
401 457
	 * names, leading things like 'https://www.github.com/' to be invalid.
402 458
	 * Instead
403 459
	 *
404 -
	 * @see https://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1
460 +
	 * @link https://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1
405 461
	 *
406 -
	 * @throws Requests_Exception On failure to connect via TLS (`fsockopen.ssl.connect_error`)
407 -
	 * @throws Requests_Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`)
408 462
	 * @param string $host Host name to verify against
409 463
	 * @param resource $context Stream context
410 464
	 * @return bool
465 +
	 *
466 +
	 * @throws \WpOrg\Requests\Exception On failure to connect via TLS (`fsockopen.ssl.connect_error`)
467 +
	 * @throws \WpOrg\Requests\Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`)
411 468
	 */
412 469
	public function verify_certificate_from_context($host, $context) {
413 470
		$meta = stream_context_get_options($context);
@@ -415,35 +472,33 @@
Loading
415 472
		// If we don't have SSL options, then we couldn't make the connection at
416 473
		// all
417 474
		if (empty($meta) || empty($meta['ssl']) || empty($meta['ssl']['peer_certificate'])) {
418 -
			throw new Requests_Exception(rtrim($this->connect_error), 'ssl.connect_error');
475 +
			throw new Exception(rtrim($this->connect_error), 'ssl.connect_error');
419 476
		}
420 477
421 478
		$cert = openssl_x509_parse($meta['ssl']['peer_certificate']);
422 479
423 -
		return Requests_SSL::verify_certificate($host, $cert);
480 +
		return Ssl::verify_certificate($host, $cert);
424 481
	}
425 482
426 483
	/**
427 -
	 * Whether this transport is valid
484 +
	 * Self-test whether the transport can be used.
485 +
	 *
486 +
	 * The available capabilities to test for can be found in {@see \WpOrg\Requests\Capability}.
428 487
	 *
429 488
	 * @codeCoverageIgnore
430 -
	 * @return boolean True if the transport is valid, false otherwise.
489 +
	 * @param array<string, bool> $capabilities Optional. Associative array of capabilities to test against, i.e. `['<capability>' => true]`.
490 +
	 * @return bool Whether the transport can be used.
431 491
	 */
432 -
	public static function test($capabilities = array()) {
492 +
	public static function test($capabilities = []) {
433 493
		if (!function_exists('fsockopen')) {
434 494
			return false;
435 495
		}
436 496
437 497
		// If needed, check that streams support SSL
438 -
		if (isset($capabilities['ssl']) && $capabilities['ssl']) {
498 +
		if (isset($capabilities[Capability::SSL]) && $capabilities[Capability::SSL]) {
439 499
			if (!extension_loaded('openssl') || !function_exists('openssl_x509_parse')) {
440 500
				return false;
441 501
			}
442 -
443 -
			// Currently broken, thanks to https://github.com/facebook/hhvm/issues/2156
444 -
			if (defined('HHVM_VERSION')) {
445 -
				return false;
446 -
			}
447 502
		}
448 503
449 504
		return true;

@@ -2,15 +2,20 @@
Loading
2 2
/**
3 3
 * Exception based on HTTP response
4 4
 *
5 -
 * @package Requests
5 +
 * @package Requests\Exceptions
6 6
 */
7 7
8 +
namespace WpOrg\Requests\Exception;
9 +
10 +
use WpOrg\Requests\Exception;
11 +
use WpOrg\Requests\Exception\Http\StatusUnknown;
12 +
8 13
/**
9 14
 * Exception based on HTTP response
10 15
 *
11 -
 * @package Requests
16 +
 * @package Requests\Exceptions
12 17
 */
13 -
class Requests_Exception_HTTP extends Requests_Exception {
18 +
class Http extends Exception {
14 19
	/**
15 20
	 * HTTP status code
16 21
	 *
@@ -44,7 +49,9 @@
Loading
44 49
	}
45 50
46 51
	/**
47 -
	 * Get the status message
52 +
	 * Get the status message.
53 +
	 *
54 +
	 * @return string
48 55
	 */
49 56
	public function getReason() {
50 57
		return $this->reason;
@@ -58,14 +65,14 @@
Loading
58 65
	 */
59 66
	public static function get_class($code) {
60 67
		if (!$code) {
61 -
			return 'Requests_Exception_HTTP_Unknown';
68 +
			return StatusUnknown::class;
62 69
		}
63 70
64 -
		$class = sprintf('Requests_Exception_HTTP_%d', $code);
71 +
		$class = sprintf('\WpOrg\Requests\Exception\Http\Status%d', $code);
65 72
		if (class_exists($class)) {
66 73
			return $class;
67 74
		}
68 75
69 -
		return 'Requests_Exception_HTTP_Unknown';
76 +
		return StatusUnknown::class;
70 77
	}
71 78
}
72 79
imilarity index 61%
73 80
ename from library/Requests/Exception/HTTP/304.php
74 81
ename to src/Exception/Http/Status304.php

@@ -0,0 +1,109 @@
Loading
1 +
<?php
2 +
/**
3 +
 * Input validation utilities.
4 +
 *
5 +
 * @package Requests\Utilities
6 +
 */
7 +
8 +
namespace WpOrg\Requests\Utility;
9 +
10 +
use ArrayAccess;
11 +
use CurlHandle;
12 +
use Traversable;
13 +
14 +
/**
15 +
 * Input validation utilities.
16 +
 *
17 +
 * @package Requests\Utilities
18 +
 */
19 +
final class InputValidator {
20 +
21 +
	/**
22 +
	 * Verify that a received input parameter is of type string or is "stringable".
23 +
	 *
24 +
	 * @param mixed $input Input parameter to verify.
25 +
	 *
26 +
	 * @return bool
27 +
	 */
28 +
	public static function is_string_or_stringable($input) {
29 +
		return is_string($input) || self::is_stringable_object($input);
30 +
	}
31 +
32 +
	/**
33 +
	 * Verify whether a received input parameter is usable as an integer array key.
34 +
	 *
35 +
	 * @param mixed $input Input parameter to verify.
36 +
	 *
37 +
	 * @return bool
38 +
	 */
39 +
	public static function is_numeric_array_key($input) {
40 +
		if (is_int($input)) {
41 +
			return true;
42 +
		}
43 +
44 +
		if (!is_string($input)) {
45 +
			return false;
46 +
		}
47 +
48 +
		return (bool) preg_match('`^-?[0-9]+$`', $input);
49 +
	}
50 +
51 +
	/**
52 +
	 * Verify whether a received input parameter is "stringable".
53 +
	 *
54 +
	 * @param mixed $input Input parameter to verify.
55 +
	 *
56 +
	 * @return bool
57 +
	 */
58 +
	public static function is_stringable_object($input) {
59 +
		return is_object($input) && method_exists($input, '__toString');
60 +
	}
61 +
62 +
	/**
63 +
	 * Verify whether a received input parameter is _accessible as if it were an array_.
64 +
	 *
65 +
	 * @param mixed $input Input parameter to verify.
66 +
	 *
67 +
	 * @return bool
68 +
	 */
69 +
	public static function has_array_access($input) {
70 +
		return is_array($input) || $input instanceof ArrayAccess;
71 +
	}
72 +
73 +
	/**
74 +
	 * Verify whether a received input parameter is "iterable".
75 +
	 *
76 +
	 * @internal The PHP native `is_iterable()` function was only introduced in PHP 7.1
77 +
	 * and this library still supports PHP 5.6.
78 +
	 *
79 +
	 * @param mixed $input Input parameter to verify.
80 +
	 *
81 +
	 * @return bool
82 +
	 */
83 +
	public static function is_iterable($input) {
84 +
		return is_array($input) || $input instanceof Traversable;
85 +
	}
86 +
87 +
	/**
88 +
	 * Verify whether a received input parameter is a Curl handle.
89 +
	 *
90 +
	 * The PHP Curl extension worked with resources prior to PHP 8.0 and with
91 +
	 * an instance of the `CurlHandle` class since PHP 8.0.
92 +
	 * {@link https://www.php.net/manual/en/migration80.incompatible.php#migration80.incompatible.resource2object}
93 +
	 *
94 +
	 * @param mixed $input Input parameter to verify.
95 +
	 *
96 +
	 * @return bool
97 +
	 */
98 +
	public static function is_curl_handle($input) {
99 +
		if (is_resource($input)) {
100 +
			return get_resource_type($input) === 'curl';
101 +
		}
102 +
103 +
		if (is_object($input)) {
104 +
			return $input instanceof CurlHandle;
105 +
		}
106 +
107 +
		return false;
108 +
	}
109 +
}

@@ -0,0 +1,187 @@
Loading
1 +
<?php
2 +
/**
3 +
 * Autoloader for Requests for PHP.
4 +
 *
5 +
 * Include this file if you'd like to avoid having to create your own autoloader.
6 +
 *
7 +
 * @package Requests
8 +
 * @since   2.0.0
9 +
 *
10 +
 * @codeCoverageIgnore
11 +
 */
12 +
13 +
namespace WpOrg\Requests;
14 +
15 +
/*
16 +
 * Ensure the autoloader is only declared once.
17 +
 * This safeguard is in place as this is the typical entry point for this library
18 +
 * and this file being required unconditionally could easily cause
19 +
 * fatal "Class already declared" errors.
20 +
 */
21 +
if (class_exists('WpOrg\Requests\Autoload') === false) {
22 +
23 +
	/**
24 +
	 * Autoloader for Requests for PHP.
25 +
	 *
26 +
	 * This autoloader supports the PSR-4 based Requests 2.0.0 classes in a case-sensitive manner
27 +
	 * as the most common server OS-es are case-sensitive and the file names are in mixed case.
28 +
	 *
29 +
	 * For the PSR-0 Requests 1.x BC-layer, requested classes will be treated case-insensitively.
30 +
	 *
31 +
	 * @package Requests
32 +
	 */
33 +
	final class Autoload {
34 +
35 +
		/**
36 +
		 * List of the old PSR-0 class names in lowercase as keys with their PSR-4 case-sensitive name as a value.
37 +
		 *
38 +
		 * @var array
39 +
		 */
40 +
		private static $deprecated_classes = [
41 +
			// Interfaces.
42 +
			'requests_auth'                              => '\WpOrg\Requests\Auth',
43 +
			'requests_hooker'                            => '\WpOrg\Requests\HookManager',
44 +
			'requests_proxy'                             => '\WpOrg\Requests\Proxy',
45 +
			'requests_transport'                         => '\WpOrg\Requests\Transport',
46 +
47 +
			// Classes.
48 +
			'requests_cookie'                            => '\WpOrg\Requests\Cookie',
49 +
			'requests_exception'                         => '\WpOrg\Requests\Exception',
50 +
			'requests_hooks'                             => '\WpOrg\Requests\Hooks',
51 +
			'requests_idnaencoder'                       => '\WpOrg\Requests\IdnaEncoder',
52 +
			'requests_ipv6'                              => '\WpOrg\Requests\Ipv6',
53 +
			'requests_iri'                               => '\WpOrg\Requests\Iri',
54 +
			'requests_response'                          => '\WpOrg\Requests\Response',
55 +
			'requests_session'                           => '\WpOrg\Requests\Session',
56 +
			'requests_ssl'                               => '\WpOrg\Requests\Ssl',
57 +
			'requests_auth_basic'                        => '\WpOrg\Requests\Auth\Basic',
58 +
			'requests_cookie_jar'                        => '\WpOrg\Requests\Cookie\Jar',
59 +
			'requests_proxy_http'                        => '\WpOrg\Requests\Proxy\Http',
60 +
			'requests_response_headers'                  => '\WpOrg\Requests\Response\Headers',
61 +
			'requests_transport_curl'                    => '\WpOrg\Requests\Transport\Curl',
62 +
			'requests_transport_fsockopen'               => '\WpOrg\Requests\Transport\Fsockopen',
63 +
			'requests_utility_caseinsensitivedictionary' => '\WpOrg\Requests\Utility\CaseInsensitiveDictionary',
64 +
			'requests_utility_filterediterator'          => '\WpOrg\Requests\Utility\FilteredIterator',
65 +
			'requests_exception_http'                    => '\WpOrg\Requests\Exception\Http',
66 +
			'requests_exception_transport'               => '\WpOrg\Requests\Exception\Transport',
67 +
			'requests_exception_transport_curl'          => '\WpOrg\Requests\Exception\Transport\Curl',
68 +
			'requests_exception_http_304'                => '\WpOrg\Requests\Exception\Http\Status304',
69 +
			'requests_exception_http_305'                => '\WpOrg\Requests\Exception\Http\Status305',
70 +
			'requests_exception_http_306'                => '\WpOrg\Requests\Exception\Http\Status306',
71 +
			'requests_exception_http_400'                => '\WpOrg\Requests\Exception\Http\Status400',
72 +
			'requests_exception_http_401'                => '\WpOrg\Requests\Exception\Http\Status401',
73 +
			'requests_exception_http_402'                => '\WpOrg\Requests\Exception\Http\Status402',
74 +
			'requests_exception_http_403'                => '\WpOrg\Requests\Exception\Http\Status403',
75 +
			'requests_exception_http_404'                => '\WpOrg\Requests\Exception\Http\Status404',
76 +
			'requests_exception_http_405'                => '\WpOrg\Requests\Exception\Http\Status405',
77 +
			'requests_exception_http_406'                => '\WpOrg\Requests\Exception\Http\Status406',
78 +
			'requests_exception_http_407'                => '\WpOrg\Requests\Exception\Http\Status407',
79 +
			'requests_exception_http_408'                => '\WpOrg\Requests\Exception\Http\Status408',
80 +
			'requests_exception_http_409'                => '\WpOrg\Requests\Exception\Http\Status409',
81 +
			'requests_exception_http_410'                => '\WpOrg\Requests\Exception\Http\Status410',
82 +
			'requests_exception_http_411'                => '\WpOrg\Requests\Exception\Http\Status411',
83 +
			'requests_exception_http_412'                => '\WpOrg\Requests\Exception\Http\Status412',
84 +
			'requests_exception_http_413'                => '\WpOrg\Requests\Exception\Http\Status413',
85 +
			'requests_exception_http_414'                => '\WpOrg\Requests\Exception\Http\Status414',
86 +
			'requests_exception_http_415'                => '\WpOrg\Requests\Exception\Http\Status415',
87 +
			'requests_exception_http_416'                => '\WpOrg\Requests\Exception\Http\Status416',
88 +
			'requests_exception_http_417'                => '\WpOrg\Requests\Exception\Http\Status417',
89 +
			'requests_exception_http_418'                => '\WpOrg\Requests\Exception\Http\Status418',
90 +
			'requests_exception_http_428'                => '\WpOrg\Requests\Exception\Http\Status428',
91 +
			'requests_exception_http_429'                => '\WpOrg\Requests\Exception\Http\Status429',
92 +
			'requests_exception_http_431'                => '\WpOrg\Requests\Exception\Http\Status431',
93 +
			'requests_exception_http_500'                => '\WpOrg\Requests\Exception\Http\Status500',
94 +
			'requests_exception_http_501'                => '\WpOrg\Requests\Exception\Http\Status501',
95 +
			'requests_exception_http_502'                => '\WpOrg\Requests\Exception\Http\Status502',
96 +
			'requests_exception_http_503'                => '\WpOrg\Requests\Exception\Http\Status503',
97 +
			'requests_exception_http_504'                => '\WpOrg\Requests\Exception\Http\Status504',
98 +
			'requests_exception_http_505'                => '\WpOrg\Requests\Exception\Http\Status505',
99 +
			'requests_exception_http_511'                => '\WpOrg\Requests\Exception\Http\Status511',
100 +
			'requests_exception_http_unknown'            => '\WpOrg\Requests\Exception\Http\StatusUnknown',
101 +
		];
102 +
103 +
		/**
104 +
		 * Register the autoloader.
105 +
		 *
106 +
		 * Note: the autoloader is *prepended* in the autoload queue.
107 +
		 * This is done to ensure that the Requests 2.0 autoloader takes precedence
108 +
		 * over a potentially (dependency-registered) Requests 1.x autoloader.
109 +
		 *
110 +
		 * @internal This method contains a safeguard against the autoloader being
111 +
		 * registered multiple times. This safeguard uses a global constant to
112 +
		 * (hopefully/in most cases) still function correctly, even if the
113 +
		 * class would be renamed.
114 +
		 *
115 +
		 * @return void
116 +
		 */
117 +
		public static function register() {
118 +
			if (defined('REQUESTS_AUTOLOAD_REGISTERED') === false) {
119 +
				spl_autoload_register([self::class, 'load'], true);
120 +
				define('REQUESTS_AUTOLOAD_REGISTERED', true);
121 +
			}
122 +
		}
123 +
124 +
		/**
125 +
		 * Autoloader.
126 +
		 *
127 +
		 * @param string $class_name Name of the class name to load.
128 +
		 *
129 +
		 * @return bool Whether a class was loaded or not.
130 +
		 */
131 +
		public static function load($class_name) {
132 +
			// Check that the class starts with "Requests" (PSR-0) or "WpOrg\Requests" (PSR-4).
133 +
			$psr_4_prefix_pos = strpos($class_name, 'WpOrg\\Requests\\');
134 +
135 +
			if (stripos($class_name, 'Requests') !== 0 && $psr_4_prefix_pos !== 0) {
136 +
				return false;
137 +
			}
138 +
139 +
			$class_lower = strtolower($class_name);
140 +
141 +
			if ($class_lower === 'requests') {
142 +
				// Reference to the original PSR-0 Requests class.
143 +
				$file = dirname(__DIR__) . '/library/Requests.php';
144 +
			} elseif ($psr_4_prefix_pos === 0) {
145 +
				// PSR-4 classname.
146 +
				$file = __DIR__ . '/' . strtr(substr($class_name, 15), '\\', '/') . '.php';
147 +
			}
148 +
149 +
			if (isset($file) && file_exists($file)) {
150 +
				include $file;
151 +
				return true;
152 +
			}
153 +
154 +
			/*
155 +
			 * Okay, so the class starts with "Requests", but we couldn't find the file.
156 +
			 * If this is one of the deprecated/renamed PSR-0 classes being requested,
157 +
			 * let's alias it to the new name and throw a deprecation notice.
158 +
			 */
159 +
			if (isset(self::$deprecated_classes[$class_lower])) {
160 +
				/*
161 +
				 * Integrators who cannot yet upgrade to the PSR-4 class names can silence deprecations
162 +
				 * by defining a `REQUESTS_SILENCE_PSR0_DEPRECATIONS` constant and setting it to `true`.
163 +
				 * The constant needs to be defined before the first deprecated class is requested
164 +
				 * via this autoloader.
165 +
				 */
166 +
				if (!defined('REQUESTS_SILENCE_PSR0_DEPRECATIONS') || REQUESTS_SILENCE_PSR0_DEPRECATIONS !== true) {
167 +
					// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
168 +
					trigger_error(
169 +
						'The PSR-0 `Requests_...` class names in the Request library are deprecated.'
170 +
						. ' Switch to the PSR-4 `WpOrg\Requests\...` class names at your earliest convenience.',
171 +
						E_USER_DEPRECATED
172 +
					);
173 +
174 +
					// Prevent the deprecation notice from being thrown twice.
175 +
					if (!defined('REQUESTS_SILENCE_PSR0_DEPRECATIONS')) {
176 +
						define('REQUESTS_SILENCE_PSR0_DEPRECATIONS', true);
177 +
					}
178 +
				}
179 +
180 +
				// Create an alias and let the autoloader recursively kick in to load the PSR-4 class.
181 +
				return class_alias(self::$deprecated_classes[$class_lower], $class_name, true);
182 +
			}
183 +
184 +
			return false;
185 +
		}
186 +
	}
187 +
}

@@ -0,0 +1,75 @@
Loading
1 +
<?php
2 +
/**
3 +
 * Port utilities for Requests
4 +
 *
5 +
 * @package Requests\Utilities
6 +
 * @since   2.0.0
7 +
 */
8 +
9 +
namespace WpOrg\Requests;
10 +
11 +
use WpOrg\Requests\Exception;
12 +
use WpOrg\Requests\Exception\InvalidArgument;
13 +
14 +
/**
15 +
 * Find the correct port depending on the Request type.
16 +
 *
17 +
 * @package Requests\Utilities
18 +
 * @since   2.0.0
19 +
 */
20 +
final class Port {
21 +
22 +
	/**
23 +
	 * Port to use with Acap requests.
24 +
	 *
25 +
	 * @var int
26 +
	 */
27 +
	const ACAP = 674;
28 +
29 +
	/**
30 +
	 * Port to use with Dictionary requests.
31 +
	 *
32 +
	 * @var int
33 +
	 */
34 +
	const DICT = 2628;
35 +
36 +
	/**
37 +
	 * Port to use with HTTP requests.
38 +
	 *
39 +
	 * @var int
40 +
	 */
41 +
	const HTTP = 80;
42 +
43 +
	/**
44 +
	 * Port to use with HTTP over SSL requests.
45 +
	 *
46 +
	 * @var int
47 +
	 */
48 +
	const HTTPS = 443;
49 +
50 +
	/**
51 +
	 * Retrieve the port number to use.
52 +
	 *
53 +
	 * @param string $type Request type.
54 +
	 *                     The following requests types are supported:
55 +
	 *                     'acap', 'dict', 'http' and 'https'.
56 +
	 *
57 +
	 * @return int
58 +
	 *
59 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When a non-string input has been passed.
60 +
	 * @throws \WpOrg\Requests\Exception                 When a non-supported port is requested ('portnotsupported').
61 +
	 */
62 +
	public static function get($type) {
63 +
		if (!is_string($type)) {
64 +
			throw InvalidArgument::create(1, '$type', 'string', gettype($type));
65 +
		}
66 +
67 +
		$type = strtoupper($type);
68 +
		if (!defined("self::{$type}")) {
69 +
			$message = sprintf('Invalid port type (%s) passed', $type);
70 +
			throw new Exception($message, 'portnotsupported');
71 +
		}
72 +
73 +
		return constant("self::{$type}");
74 +
	}
75 +
}

@@ -2,10 +2,17 @@
Loading
2 2
/**
3 3
 * Session handler for persistent requests and default parameters
4 4
 *
5 -
 * @package Requests
6 -
 * @subpackage Session Handler
5 +
 * @package Requests\SessionHandler
7 6
 */
8 7
8 +
namespace WpOrg\Requests;
9 +
10 +
use WpOrg\Requests\Cookie\Jar;
11 +
use WpOrg\Requests\Exception\InvalidArgument;
12 +
use WpOrg\Requests\Iri;
13 +
use WpOrg\Requests\Requests;
14 +
use WpOrg\Requests\Utility\InputValidator;
15 +
9 16
/**
10 17
 * Session handler for persistent requests and default parameters
11 18
 *
@@ -14,10 +21,9 @@
Loading
14 21
 * with all subrequests resolved from this. Base options can be set (including
15 22
 * a shared cookie jar), then overridden for individual requests.
16 23
 *
17 -
 * @package Requests
18 -
 * @subpackage Session Handler
24 +
 * @package Requests\SessionHandler
19 25
 */
20 -
class Requests_Session {
26 +
class Session {
21 27
	/**
22 28
	 * Base URL for requests
23 29
	 *
@@ -32,7 +38,7 @@
Loading
32 38
	 *
33 39
	 * @var array
34 40
	 */
35 -
	public $headers = array();
41 +
	public $headers = [];
36 42
37 43
	/**
38 44
	 * Base data for requests
@@ -42,7 +48,7 @@
Loading
42 48
	 *
43 49
	 * @var array
44 50
	 */
45 -
	public $data = array();
51 +
	public $data = [];
46 52
47 53
	/**
48 54
	 * Base options for requests
@@ -55,36 +61,57 @@
Loading
55 61
	 *
56 62
	 * @var array
57 63
	 */
58 -
	public $options = array();
64 +
	public $options = [];
59 65
60 66
	/**
61 67
	 * Create a new session
62 68
	 *
63 -
	 * @param string|null $url Base URL for requests
69 +
	 * @param string|Stringable|null $url Base URL for requests
64 70
	 * @param array $headers Default headers for requests
65 71
	 * @param array $data Default data for requests
66 72
	 * @param array $options Default options for requests
73 +
	 *
74 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string, Stringable or null.
75 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $headers argument is not an array.
76 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $data argument is not an array.
77 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
67 78
	 */
68 -
	public function __construct($url = null, $headers = array(), $data = array(), $options = array()) {
79 +
	public function __construct($url = null, $headers = [], $data = [], $options = []) {
80 +
		if ($url !== null && InputValidator::is_string_or_stringable($url) === false) {
81 +
			throw InvalidArgument::create(1, '$url', 'string|Stringable|null', gettype($url));
82 +
		}
83 +
84 +
		if (is_array($headers) === false) {
85 +
			throw InvalidArgument::create(2, '$headers', 'array', gettype($headers));
86 +
		}
87 +
88 +
		if (is_array($data) === false) {
89 +
			throw InvalidArgument::create(3, '$data', 'array', gettype($data));
90 +
		}
91 +
92 +
		if (is_array($options) === false) {
93 +
			throw InvalidArgument::create(4, '$options', 'array', gettype($options));
94 +
		}
95 +
69 96
		$this->url     = $url;
70 97
		$this->headers = $headers;
71 98
		$this->data    = $data;
72 99
		$this->options = $options;
73 100
74 101
		if (empty($this->options['cookies'])) {
75 -
			$this->options['cookies'] = new Requests_Cookie_Jar();
102 +
			$this->options['cookies'] = new Jar();
76 103
		}
77 104
	}
78 105
79 106
	/**
80 107
	 * Get a property's value
81 108
	 *
82 -
	 * @param string $key Property key
109 +
	 * @param string $name Property name.
83 110
	 * @return mixed|null Property value, null if none found
84 111
	 */
85 -
	public function __get($key) {
86 -
		if (isset($this->options[$key])) {
87 -
			return $this->options[$key];
112 +
	public function __get($name) {
113 +
		if (isset($this->options[$name])) {
114 +
			return $this->options[$name];
88 115
		}
89 116
90 117
		return null;
@@ -93,93 +120,91 @@
Loading
93 120
	/**
94 121
	 * Set a property's value
95 122
	 *
96 -
	 * @param string $key Property key
123 +
	 * @param string $name Property name.
97 124
	 * @param mixed $value Property value
98 125
	 */
99 -
	public function __set($key, $value) {
100 -
		$this->options[$key] = $value;
126 +
	public function __set($name, $value) {
127 +
		$this->options[$name] = $value;
101 128
	}
102 129
103 130
	/**
104 131
	 * Remove a property's value
105 132
	 *
106 -
	 * @param string $key Property key
133 +
	 * @param string $name Property name.
107 134
	 */
108 -
	public function __isset($key) {
109 -
		return isset($this->options[$key]);
135 +
	public function __isset($name) {
136 +
		return isset($this->options[$name]);
110 137
	}
111 138
112 139
	/**
113 140
	 * Remove a property's value
114 141
	 *
115 -
	 * @param string $key Property key
142 +
	 * @param string $name Property name.
116 143
	 */
117 -
	public function __unset($key) {
118 -
		if (isset($this->options[$key])) {
119 -
			unset($this->options[$key]);
120 -
		}
144 +
	public function __unset($name) {
145 +
		unset($this->options[$name]);
121 146
	}
122 147
123 148
	/**#@+
124 -
	 * @see request()
149 +
	 * @see \WpOrg\Requests\Session::request()
125 150
	 * @param string $url
126 151
	 * @param array $headers
127 152
	 * @param array $options
128 -
	 * @return Requests_Response
153 +
	 * @return \WpOrg\Requests\Response
129 154
	 */
130 155
	/**
131 156
	 * Send a GET request
132 157
	 */
133 -
	public function get($url, $headers = array(), $options = array()) {
158 +
	public function get($url, $headers = [], $options = []) {
134 159
		return $this->request($url, $headers, null, Requests::GET, $options);
135 160
	}
136 161
137 162
	/**
138 163
	 * Send a HEAD request
139 164
	 */
140 -
	public function head($url, $headers = array(), $options = array()) {
165 +
	public function head($url, $headers = [], $options = []) {
141 166
		return $this->request($url, $headers, null, Requests::HEAD, $options);
142 167
	}
143 168
144 169
	/**
145 170
	 * Send a DELETE request
146 171
	 */
147 -
	public function delete($url, $headers = array(), $options = array()) {
172 +
	public function delete($url, $headers = [], $options = []) {
148 173
		return $this->request($url, $headers, null, Requests::DELETE, $options);
149 174
	}
150 175
	/**#@-*/
151 176
152 177
	/**#@+
153 -
	 * @see request()
178 +
	 * @see \WpOrg\Requests\Session::request()
154 179
	 * @param string $url
155 180
	 * @param array $headers
156 181
	 * @param array $data
157 182
	 * @param array $options
158 -
	 * @return Requests_Response
183 +
	 * @return \WpOrg\Requests\Response
159 184
	 */
160 185
	/**
161 186
	 * Send a POST request
162 187
	 */
163 -
	public function post($url, $headers = array(), $data = array(), $options = array()) {
188 +
	public function post($url, $headers = [], $data = [], $options = []) {
164 189
		return $this->request($url, $headers, $data, Requests::POST, $options);
165 190
	}
166 191
167 192
	/**
168 193
	 * Send a PUT request
169 194
	 */
170 -
	public function put($url, $headers = array(), $data = array(), $options = array()) {
195 +
	public function put($url, $headers = [], $data = [], $options = []) {
171 196
		return $this->request($url, $headers, $data, Requests::PUT, $options);
172 197
	}
173 198
174 199
	/**
175 200
	 * Send a PATCH request
176 201
	 *
177 -
	 * Note: Unlike {@see post} and {@see put}, `$headers` is required, as the
178 -
	 * specification recommends that should send an ETag
202 +
	 * Note: Unlike {@see \WpOrg\Requests\Session::post()} and {@see \WpOrg\Requests\Session::put()},
203 +
	 * `$headers` is required, as the specification recommends that should send an ETag
179 204
	 *
180 205
	 * @link https://tools.ietf.org/html/rfc5789
181 206
	 */
182 -
	public function patch($url, $headers, $data = array(), $options = array()) {
207 +
	public function patch($url, $headers, $data = [], $options = []) {
183 208
		return $this->request($url, $headers, $data, Requests::PATCH, $options);
184 209
	}
185 210
	/**#@-*/
@@ -190,18 +215,18 @@
Loading
190 215
	 * This method initiates a request and sends it via a transport before
191 216
	 * parsing.
192 217
	 *
193 -
	 * @see Requests::request()
194 -
	 *
195 -
	 * @throws Requests_Exception On invalid URLs (`nonhttp`)
218 +
	 * @see \WpOrg\Requests\Requests::request()
196 219
	 *
197 220
	 * @param string $url URL to request
198 221
	 * @param array $headers Extra headers to send with the request
199 222
	 * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
200 -
	 * @param string $type HTTP request type (use Requests constants)
201 -
	 * @param array $options Options for the request (see {@see Requests::request})
202 -
	 * @return Requests_Response
223 +
	 * @param string $type HTTP request type (use \WpOrg\Requests\Requests constants)
224 +
	 * @param array $options Options for the request (see {@see \WpOrg\Requests\Requests::request()})
225 +
	 * @return \WpOrg\Requests\Response
226 +
	 *
227 +
	 * @throws \WpOrg\Requests\Exception On invalid URLs (`nonhttp`)
203 228
	 */
204 -
	public function request($url, $headers = array(), $data = array(), $type = Requests::GET, $options = array()) {
229 +
	public function request($url, $headers = [], $data = [], $type = Requests::GET, $options = []) {
205 230
		$request = $this->merge_request(compact('url', 'headers', 'data', 'options'));
206 231
207 232
		return Requests::request($request['url'], $request['headers'], $request['data'], $type, $request['options']);
@@ -210,13 +235,24 @@
Loading
210 235
	/**
211 236
	 * Send multiple HTTP requests simultaneously
212 237
	 *
213 -
	 * @see Requests::request_multiple()
238 +
	 * @see \WpOrg\Requests\Requests::request_multiple()
239 +
	 *
240 +
	 * @param array $requests Requests data (see {@see \WpOrg\Requests\Requests::request_multiple()})
241 +
	 * @param array $options Global and default options (see {@see \WpOrg\Requests\Requests::request()})
242 +
	 * @return array Responses (either \WpOrg\Requests\Response or a \WpOrg\Requests\Exception object)
214 243
	 *
215 -
	 * @param array $requests Requests data (see {@see Requests::request_multiple})
216 -
	 * @param array $options Global and default options (see {@see Requests::request})
217 -
	 * @return array Responses (either Requests_Response or a Requests_Exception object)
244 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access.
245 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
218 246
	 */
219 -
	public function request_multiple($requests, $options = array()) {
247 +
	public function request_multiple($requests, $options = []) {
248 +
		if (InputValidator::has_array_access($requests) === false || InputValidator::is_iterable($requests) === false) {
249 +
			throw InvalidArgument::create(1, '$requests', 'array|ArrayAccess&Traversable', gettype($requests));
250 +
		}
251 +
252 +
		if (is_array($options) === false) {
253 +
			throw InvalidArgument::create(2, '$options', 'array', gettype($options));
254 +
		}
255 +
220 256
		foreach ($requests as $key => $request) {
221 257
			$requests[$key] = $this->merge_request($request, false);
222 258
		}
@@ -232,18 +268,18 @@
Loading
232 268
	/**
233 269
	 * Merge a request's data with the default data
234 270
	 *
235 -
	 * @param array $request Request data (same form as {@see request_multiple})
271 +
	 * @param array $request Request data (same form as {@see \WpOrg\Requests\Session::request_multiple()})
236 272
	 * @param boolean $merge_options Should we merge options as well?
237 273
	 * @return array Request data
238 274
	 */
239 275
	protected function merge_request($request, $merge_options = true) {
240 276
		if ($this->url !== null) {
241 -
			$request['url'] = Requests_IRI::absolutize($this->url, $request['url']);
277 +
			$request['url'] = Iri::absolutize($this->url, $request['url']);
242 278
			$request['url'] = $request['url']->uri;
243 279
		}
244 280
245 281
		if (empty($request['headers'])) {
246 -
			$request['headers'] = array();
282 +
			$request['headers'] = [];
247 283
		}
248 284
		$request['headers'] = array_merge($this->headers, $request['headers']);
249 285
@@ -256,7 +292,7 @@
Loading
256 292
			$request['data'] = array_merge($this->data, $request['data']);
257 293
		}
258 294
259 -
		if ($merge_options !== false) {
295 +
		if ($merge_options === true) {
260 296
			$request['options'] = array_merge($this->options, $request['options']);
261 297
262 298
			// Disallow forcing the type, as that's a per request setting
263 299
imilarity index 55%
264 300
ename from library/Requests/SSL.php
265 301
ename to src/Ssl.php

@@ -2,21 +2,26 @@
Loading
2 2
/**
3 3
 * HTTP Proxy connection interface
4 4
 *
5 -
 * @package Requests
6 -
 * @subpackage Proxy
7 -
 * @since 1.6
5 +
 * @package Requests\Proxy
6 +
 * @since   1.6
8 7
 */
9 8
9 +
namespace WpOrg\Requests\Proxy;
10 +
11 +
use WpOrg\Requests\Exception\ArgumentCount;
12 +
use WpOrg\Requests\Exception\InvalidArgument;
13 +
use WpOrg\Requests\Hooks;
14 +
use WpOrg\Requests\Proxy;
15 +
10 16
/**
11 17
 * HTTP Proxy connection interface
12 18
 *
13 19
 * Provides a handler for connection via an HTTP proxy
14 20
 *
15 -
 * @package Requests
16 -
 * @subpackage Proxy
17 -
 * @since 1.6
21 +
 * @package Requests\Proxy
22 +
 * @since   1.6
18 23
 */
19 -
class Requests_Proxy_HTTP implements Requests_Proxy {
24 +
final class Http implements Proxy {
20 25
	/**
21 26
	 * Proxy host and port
22 27
	 *
@@ -51,8 +56,13 @@
Loading
51 56
	 * Constructor
52 57
	 *
53 58
	 * @since 1.6
54 -
	 * @throws Requests_Exception On incorrect number of arguments (`authbasicbadargs`)
55 -
	 * @param array|null $args Array of user and password. Must have exactly two elements
59 +
	 *
60 +
	 * @param array|string|null $args Proxy as a string or an array of proxy, user and password.
61 +
	 *                                When passed as an array, must have exactly one (proxy)
62 +
	 *                                or three elements (proxy, user, password).
63 +
	 *
64 +
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not an array, a string or null.
65 +
	 * @throws \WpOrg\Requests\Exception\ArgumentCount On incorrect number of arguments (`proxyhttpbadargs`)
56 66
	 */
57 67
	public function __construct($args = null) {
58 68
		if (is_string($args)) {
@@ -67,8 +77,14 @@
Loading
67 77
				$this->use_authentication                    = true;
68 78
			}
69 79
			else {
70 -
				throw new Requests_Exception('Invalid number of arguments', 'proxyhttpbadargs');
80 +
				throw ArgumentCount::create(
81 +
					'an array with exactly one element or exactly three elements',
82 +
					count($args),
83 +
					'proxyhttpbadargs'
84 +
				);
71 85
			}
86 +
		} elseif ($args !== null) {
87 +
			throw InvalidArgument::create(1, '$args', 'array|string|null', gettype($args));
72 88
		}
73 89
	}
74 90
@@ -76,19 +92,19 @@
Loading
76 92
	 * Register the necessary callbacks
77 93
	 *
78 94
	 * @since 1.6
79 -
	 * @see curl_before_send
80 -
	 * @see fsockopen_remote_socket