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