rmccue /
Requests
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 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 | /** |
||
| 13 | * Requests for PHP |
||
| 14 | * |
||
| 15 | * Inspired by Requests for Python. |
||
| 16 | * |
||
| 17 | * Based on concepts from SimplePie_File, RequestCore and WP_Http. |
||
| 18 | * |
||
| 19 | * @package Requests |
||
| 20 | */ |
||
| 21 | class Requests { |
||
| 22 | /** |
||
| 23 | * POST method |
||
| 24 | * |
||
| 25 | * @var string |
||
| 26 | */ |
||
| 27 | const POST = 'POST'; |
||
| 28 | |||
| 29 | /** |
||
| 30 | * PUT method |
||
| 31 | * |
||
| 32 | * @var string |
||
| 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.7'; |
||
| 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 | * |
||
| 134 | * @codeCoverageIgnore |
||
| 135 | * |
||
| 136 | * @param string $class Class name to load |
||
| 137 | */ |
||
| 138 | public static function autoloader($class) { |
||
| 139 | // Check that the class starts with "Requests" |
||
| 140 | if (strpos($class, 'Requests') !== 0) { |
||
| 141 | return; |
||
| 142 | } |
||
| 143 | |||
| 144 | $file = str_replace('_', '/', $class); |
||
| 145 | if (file_exists(dirname(__FILE__) . '/' . $file . '.php')) { |
||
| 146 | require_once dirname(__FILE__) . '/' . $file . '.php'; |
||
| 147 | } |
||
| 148 | } |
||
| 149 | |||
| 150 | /** |
||
| 151 | * Register the built-in autoloader |
||
| 152 | * |
||
| 153 | * @codeCoverageIgnore |
||
| 154 | */ |
||
| 155 | public static function register_autoloader() { |
||
| 156 | spl_autoload_register(array('Requests', 'autoloader')); |
||
| 157 | } |
||
| 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']); |
||
|
0 ignored issues
–
show
|
|||
| 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 | View Code Duplication | 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 | } |
||
| 1001 | } |
||
| 1002 |
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.