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