| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980 | <?php/** * Requests for PHP * * Inspired by Requests for Python. * * Based on concepts from SimplePie_File, RequestCore and WP_Http. * * @package Requests *//** * Requests for PHP * * Inspired by Requests for Python. * * Based on concepts from SimplePie_File, RequestCore and WP_Http. * * @package Requests */class Requests {	/**	 * POST method	 *	 * @var string	 */	const POST = 'POST';	/**	 * PUT method	 *	 * @var string	 */	const PUT = 'PUT';	/**	 * GET method	 *	 * @var string	 */	const GET = 'GET';	/**	 * HEAD method	 *	 * @var string	 */	const HEAD = 'HEAD';	/**	 * DELETE method	 *	 * @var string	 */	const DELETE = 'DELETE';	/**	 * OPTIONS method	 *	 * @var string	 */	const OPTIONS = 'OPTIONS';	/**	 * TRACE method	 *	 * @var string	 */	const TRACE = 'TRACE';	/**	 * PATCH method	 *	 * @link https://tools.ietf.org/html/rfc5789	 * @var string	 */	const PATCH = 'PATCH';	/**	 * Default size of buffer size to read streams	 *	 * @var integer	 */	const BUFFER_SIZE = 1160;	/**	 * Current version of Requests	 *	 * @var string	 */	const VERSION = '1.7-3470169';	/**	 * Registered transport classes	 *	 * @var array	 */	protected static $transports = array();	/**	 * Selected transport name	 *	 * Use {@see get_transport()} instead	 *	 * @var array	 */	public static $transport = array();	/**	 * Default certificate path.	 *	 * @see Requests::get_certificate_path()	 * @see Requests::set_certificate_path()	 *	 * @var string	 */	protected static $certificate_path;	/**	 * This is a static class, do not instantiate it	 *	 * @codeCoverageIgnore	 */	private function __construct() {}	/**	 * Autoloader for Requests	 *	 * Register this with {@see register_autoloader()} if you'd like to avoid	 * having to create your own.	 *	 * (You can also use `spl_autoload_register` directly if you'd prefer.)	 *	 * @codeCoverageIgnore	 *	 * @param string $class Class name to load	 */	public static function autoloader($class) {		// Check that the class starts with "Requests"		if (strpos($class, 'Requests') !== 0) {			return;		}		$file = str_replace('_', '/', $class);		if (file_exists(dirname(__FILE__) . '/' . $file . '.php')) {			require_once(dirname(__FILE__) . '/' . $file . '.php');		}	}	/**	 * Register the built-in autoloader	 *	 * @codeCoverageIgnore	 */	public static function register_autoloader() {		spl_autoload_register(array('Requests', 'autoloader'));	}	/**	 * Register a transport	 *	 * @param string $transport Transport class to add, must support the Requests_Transport interface	 */	public static function add_transport($transport) {		if (empty(self::$transports)) {			self::$transports = array(				'Requests_Transport_cURL',				'Requests_Transport_fsockopen',			);		}		self::$transports = array_merge(self::$transports, array($transport));	}	/**	 * Get a working transport	 *	 * @throws Requests_Exception If no valid transport is found (`notransport`)	 * @return Requests_Transport	 */	protected static function get_transport($capabilities = array()) {		// Caching code, don't bother testing coverage		// @codeCoverageIgnoreStart		// array of capabilities as a string to be used as an array key		ksort($capabilities);		$cap_string = serialize($capabilities);		// Don't search for a transport if it's already been done for these $capabilities		if (isset(self::$transport[$cap_string]) && self::$transport[$cap_string] !== null) {			return new self::$transport[$cap_string]();		}		// @codeCoverageIgnoreEnd		if (empty(self::$transports)) {			self::$transports = array(				'Requests_Transport_cURL',				'Requests_Transport_fsockopen',			);		}		// Find us a working transport		foreach (self::$transports as $class) {			if (!class_exists($class)) {				continue;			}			$result = call_user_func(array($class, 'test'), $capabilities);			if ($result) {				self::$transport[$cap_string] = $class;				break;			}		}		if (self::$transport[$cap_string] === null) {			throw new Requests_Exception('No working transports found', 'notransport', self::$transports);		}		return new self::$transport[$cap_string]();	}	/**#@+	 * @see request()	 * @param string $url	 * @param array $headers	 * @param array $options	 * @return Requests_Response	 */	/**	 * Send a GET request	 */	public static function get($url, $headers = array(), $options = array()) {		return self::request($url, $headers, null, self::GET, $options);	}	/**	 * Send a HEAD request	 */	public static function head($url, $headers = array(), $options = array()) {		return self::request($url, $headers, null, self::HEAD, $options);	}	/**	 * Send a DELETE request	 */	public static function delete($url, $headers = array(), $options = array()) {		return self::request($url, $headers, null, self::DELETE, $options);	}	/**	 * Send a TRACE request	 */	public static function trace($url, $headers = array(), $options = array()) {		return self::request($url, $headers, null, self::TRACE, $options);	}	/**#@-*/	/**#@+	 * @see request()	 * @param string $url	 * @param array $headers	 * @param array $data	 * @param array $options	 * @return Requests_Response	 */	/**	 * Send a POST request	 */	public static function post($url, $headers = array(), $data = array(), $options = array()) {		return self::request($url, $headers, $data, self::POST, $options);	}	/**	 * Send a PUT request	 */	public static function put($url, $headers = array(), $data = array(), $options = array()) {		return self::request($url, $headers, $data, self::PUT, $options);	}	/**	 * Send an OPTIONS request	 */	public static function options($url, $headers = array(), $data = array(), $options = array()) {		return self::request($url, $headers, $data, self::OPTIONS, $options);	}	/**	 * Send a PATCH request	 *	 * Note: Unlike {@see post} and {@see put}, `$headers` is required, as the	 * specification recommends that should send an ETag	 *	 * @link https://tools.ietf.org/html/rfc5789	 */	public static function patch($url, $headers, $data = array(), $options = array()) {		return self::request($url, $headers, $data, self::PATCH, $options);	}	/**#@-*/	/**	 * Main interface for HTTP requests	 *	 * This method initiates a request and sends it via a transport before	 * parsing.	 *	 * The `$options` parameter takes an associative array with the following	 * options:	 *	 * - `timeout`: How long should we wait for a response?	 *    Note: for cURL, a minimum of 1 second applies, as DNS resolution	 *    operates at second-resolution only.	 *    (float, seconds with a millisecond precision, default: 10, example: 0.01)	 * - `connect_timeout`: How long should we wait while trying to connect?	 *    (float, seconds with a millisecond precision, default: 10, example: 0.01)	 * - `useragent`: Useragent to send to the server	 *    (string, default: php-requests/$version)	 * - `follow_redirects`: Should we follow 3xx redirects?	 *    (boolean, default: true)	 * - `redirects`: How many times should we redirect before erroring?	 *    (integer, default: 10)	 * - `blocking`: Should we block processing on this request?	 *    (boolean, default: true)	 * - `filename`: File to stream the body to instead.	 *    (string|boolean, default: false)	 * - `auth`: Authentication handler or array of user/password details to use	 *    for Basic authentication	 *    (Requests_Auth|array|boolean, default: false)	 * - `proxy`: Proxy details to use for proxy by-passing and authentication	 *    (Requests_Proxy|array|string|boolean, default: false)	 * - `max_bytes`: Limit for the response body size.	 *    (integer|boolean, default: false)	 * - `idn`: Enable IDN parsing	 *    (boolean, default: true)	 * - `transport`: Custom transport. Either a class name, or a	 *    transport object. Defaults to the first working transport from	 *    {@see getTransport()}	 *    (string|Requests_Transport, default: {@see getTransport()})	 * - `hooks`: Hooks handler.	 *    (Requests_Hooker, default: new Requests_Hooks())	 * - `verify`: Should we verify SSL certificates? Allows passing in a custom	 *    certificate file as a string. (Using true uses the system-wide root	 *    certificate store instead, but this may have different behaviour	 *    across transports.)	 *    (string|boolean, default: library/Requests/Transport/cacert.pem)	 * - `verifyname`: Should we verify the common name in the SSL certificate?	 *    (boolean: default, true)	 * - `data_format`: How should we send the `$data` parameter?	 *    (string, one of 'query' or 'body', default: 'query' for	 *    HEAD/GET/DELETE, 'body' for POST/PUT/OPTIONS/PATCH)	 *	 * @throws Requests_Exception On invalid URLs (`nonhttp`)	 *	 * @param string $url URL to request	 * @param array $headers Extra headers to send with the request	 * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests	 * @param string $type HTTP request type (use Requests constants)	 * @param array $options Options for the request (see description for more information)	 * @return Requests_Response	 */	public static function request($url, $headers = array(), $data = array(), $type = self::GET, $options = array()) {		if (empty($options['type'])) {			$options['type'] = $type;		}		$options = array_merge(self::get_default_options(), $options);		self::set_defaults($url, $headers, $data, $type, $options);		$options['hooks']->dispatch('requests.before_request', array(&$url, &$headers, &$data, &$type, &$options));		if (!empty($options['transport'])) {			$transport = $options['transport'];			if (is_string($options['transport'])) {				$transport = new $transport();			}		}		else {			$need_ssl = (0 === stripos($url, 'https://'));			$capabilities = array('ssl' => $need_ssl);			$transport = self::get_transport($capabilities);		}		$response = $transport->request($url, $headers, $data, $options);		$options['hooks']->dispatch('requests.before_parse', array(&$response, $url, $headers, $data, $type, $options));		return self::parse_response($response, $url, $headers, $data, $options);	}	/**	 * Send multiple HTTP requests simultaneously	 *	 * The `$requests` parameter takes an associative or indexed array of	 * request fields. The key of each request can be used to match up the	 * request with the returned data, or with the request passed into your	 * `multiple.request.complete` callback.	 *	 * The request fields value is an associative array with the following keys:	 *	 * - `url`: Request URL Same as the `$url` parameter to	 *    {@see Requests::request}	 *    (string, required)	 * - `headers`: Associative array of header fields. Same as the `$headers`	 *    parameter to {@see Requests::request}	 *    (array, default: `array()`)	 * - `data`: Associative array of data fields or a string. Same as the	 *    `$data` parameter to {@see Requests::request}	 *    (array|string, default: `array()`)	 * - `type`: HTTP request type (use Requests constants). Same as the `$type`	 *    parameter to {@see Requests::request}	 *    (string, default: `Requests::GET`)	 * - `cookies`: Associative array of cookie name to value, or cookie jar.	 *    (array|Requests_Cookie_Jar)	 *	 * If the `$options` parameter is specified, individual requests will	 * inherit options from it. This can be used to use a single hooking system,	 * or set all the types to `Requests::POST`, for example.	 *	 * In addition, the `$options` parameter takes the following global options:	 *	 * - `complete`: A callback for when a request is complete. Takes two	 *    parameters, a Requests_Response/Requests_Exception reference, and the	 *    ID from the request array (Note: this can also be overridden on a	 *    per-request basis, although that's a little silly)	 *    (callback)	 *	 * @param array $requests Requests data (see description for more information)	 * @param array $options Global and default options (see {@see Requests::request})	 * @return array Responses (either Requests_Response or a Requests_Exception object)	 */	public static function request_multiple($requests, $options = array()) {		$options = array_merge(self::get_default_options(true), $options);		if (!empty($options['hooks'])) {			$options['hooks']->register('transport.internal.parse_response', array('Requests', 'parse_multiple'));			if (!empty($options['complete'])) {				$options['hooks']->register('multiple.request.complete', $options['complete']);			}		}		foreach ($requests as $id => &$request) {			if (!isset($request['headers'])) {				$request['headers'] = array();			}			if (!isset($request['data'])) {				$request['data'] = array();			}			if (!isset($request['type'])) {				$request['type'] = self::GET;			}			if (!isset($request['options'])) {				$request['options'] = $options;				$request['options']['type'] = $request['type'];			}			else {				if (empty($request['options']['type'])) {					$request['options']['type'] = $request['type'];				}				$request['options'] = array_merge($options, $request['options']);			}			self::set_defaults($request['url'], $request['headers'], $request['data'], $request['type'], $request['options']);			// Ensure we only hook in once			if ($request['options']['hooks'] !== $options['hooks']) {				$request['options']['hooks']->register('transport.internal.parse_response', array('Requests', 'parse_multiple'));				if (!empty($request['options']['complete'])) {					$request['options']['hooks']->register('multiple.request.complete', $request['options']['complete']);				}			}		}		unset($request);		if (!empty($options['transport'])) {			$transport = $options['transport'];			if (is_string($options['transport'])) {				$transport = new $transport();			}		}		else {			$transport = self::get_transport();		}		$responses = $transport->request_multiple($requests, $options);		foreach ($responses as $id => &$response) {			// If our hook got messed with somehow, ensure we end up with the			// correct response			if (is_string($response)) {				$request = $requests[$id];				self::parse_multiple($response, $request);				$request['options']['hooks']->dispatch('multiple.request.complete', array(&$response, $id));			}		}		return $responses;	}	/**	 * Get the default options	 *	 * @see Requests::request() for values returned by this method	 * @param boolean $multirequest Is this a multirequest?	 * @return array Default option values	 */	protected static function get_default_options($multirequest = false) {		$defaults = array(			'timeout' => 10,			'connect_timeout' => 10,			'useragent' => 'php-requests/' . self::VERSION,			'protocol_version' => 1.1,			'redirected' => 0,			'redirects' => 10,			'follow_redirects' => true,			'blocking' => true,			'type' => self::GET,			'filename' => false,			'auth' => false,			'proxy' => false,			'cookies' => false,			'max_bytes' => false,			'idn' => true,			'hooks' => null,			'transport' => null,			'verify' => Requests::get_certificate_path(),			'verifyname' => true,		);		if ($multirequest !== false) {			$defaults['complete'] = null;		}		return $defaults;	}	/**	 * Get default certificate path.	 *	 * @return string Default certificate path.	 */	public static function get_certificate_path() {		if ( ! empty( Requests::$certificate_path ) ) {			return Requests::$certificate_path;		}		return dirname(__FILE__) . '/Requests/Transport/cacert.pem';	}	/**	 * Set default certificate path.	 *	 * @param string $path Certificate path, pointing to a PEM file.	 */	public static function set_certificate_path( $path ) {		Requests::$certificate_path = $path;	}	/**	 * Set the default values	 *	 * @param string $url URL to request	 * @param array $headers Extra headers to send with the request	 * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests	 * @param string $type HTTP request type	 * @param array $options Options for the request	 * @return array $options	 */	protected static function set_defaults(&$url, &$headers, &$data, &$type, &$options) {		if (!preg_match('/^http(s)?:\/\//i', $url, $matches)) {			throw new Requests_Exception('Only HTTP(S) requests are handled.', 'nonhttp', $url);		}		if (empty($options['hooks'])) {			$options['hooks'] = new Requests_Hooks();		}		if (is_array($options['auth'])) {			$options['auth'] = new Requests_Auth_Basic($options['auth']);		}		if ($options['auth'] !== false) {			$options['auth']->register($options['hooks']);		}		if (is_string($options['proxy']) || is_array($options['proxy'])) {			$options['proxy'] = new Requests_Proxy_HTTP($options['proxy']);		}		if ($options['proxy'] !== false) {			$options['proxy']->register($options['hooks']);		}		if (is_array($options['cookies'])) {			$options['cookies'] = new Requests_Cookie_Jar($options['cookies']);		}		elseif (empty($options['cookies'])) {			$options['cookies'] = new Requests_Cookie_Jar();		}		if ($options['cookies'] !== false) {			$options['cookies']->register($options['hooks']);		}		if ($options['idn'] !== false) {			$iri = new Requests_IRI($url);			$iri->host = Requests_IDNAEncoder::encode($iri->ihost);			$url = $iri->uri;		}		// Massage the type to ensure we support it.		$type = strtoupper($type);		if (!isset($options['data_format'])) {			if (in_array($type, array(self::HEAD, self::GET, self::DELETE))) {				$options['data_format'] = 'query';			}			else {				$options['data_format'] = 'body';			}		}	}	/**	 * HTTP response parser	 *	 * @throws Requests_Exception On missing head/body separator (`requests.no_crlf_separator`)	 * @throws Requests_Exception On missing head/body separator (`noversion`)	 * @throws Requests_Exception On missing head/body separator (`toomanyredirects`)	 *	 * @param string $headers Full response text including headers and body	 * @param string $url Original request URL	 * @param array $req_headers Original $headers array passed to {@link request()}, in case we need to follow redirects	 * @param array $req_data Original $data array passed to {@link request()}, in case we need to follow redirects	 * @param array $options Original $options array passed to {@link request()}, in case we need to follow redirects	 * @return Requests_Response	 */	protected static function parse_response($headers, $url, $req_headers, $req_data, $options) {		$return = new Requests_Response();		if (!$options['blocking']) {			return $return;		}		$return->raw = $headers;		$return->url = $url;		if (!$options['filename']) {			if (($pos = strpos($headers, "\r\n\r\n")) === false) {				// Crap!				throw new Requests_Exception('Missing header/body separator', 'requests.no_crlf_separator');			}			$headers = substr($return->raw, 0, $pos);			$return->body = substr($return->raw, $pos + strlen("\n\r\n\r"));		}		else {			$return->body = '';		}		// Pretend CRLF = LF for compatibility (RFC 2616, section 19.3)		$headers = str_replace("\r\n", "\n", $headers);		// Unfold headers (replace [CRLF] 1*( SP | HT ) with SP) as per RFC 2616 (section 2.2)		$headers = preg_replace('/\n[ \t]/', ' ', $headers);		$headers = explode("\n", $headers);		preg_match('#^HTTP/(1\.\d)[ \t]+(\d+)#i', array_shift($headers), $matches);		if (empty($matches)) {			throw new Requests_Exception('Response could not be parsed', 'noversion', $headers);		}		$return->protocol_version = (float) $matches[1];		$return->status_code = (int) $matches[2];		if ($return->status_code >= 200 && $return->status_code < 300) {			$return->success = true;		}		foreach ($headers as $header) {			list($key, $value) = explode(':', $header, 2);			$value = trim($value);			preg_replace('#(\s+)#i', ' ', $value);			$return->headers[$key] = $value;		}		if (isset($return->headers['transfer-encoding'])) {			$return->body = self::decode_chunked($return->body);			unset($return->headers['transfer-encoding']);		}		if (isset($return->headers['content-encoding'])) {			$return->body = self::decompress($return->body);		}		//fsockopen and cURL compatibility		if (isset($return->headers['connection'])) {			unset($return->headers['connection']);		}		$options['hooks']->dispatch('requests.before_redirect_check', array(&$return, $req_headers, $req_data, $options));		if ($return->is_redirect() && $options['follow_redirects'] === true) {			if (isset($return->headers['location']) && $options['redirected'] < $options['redirects']) {				if ($return->status_code === 303) {					$options['type'] = self::GET;				}				$options['redirected']++;				$location = $return->headers['location'];				if (strpos($location, 'http://') !== 0 && strpos($location, 'https://') !== 0) {					// relative redirect, for compatibility make it absolute					$location = Requests_IRI::absolutize($url, $location);					$location = $location->uri;				}				$hook_args = array(					&$location,					&$req_headers,					&$req_data,					&$options,					$return				);				$options['hooks']->dispatch('requests.before_redirect', $hook_args);				$redirected = self::request($location, $req_headers, $req_data, $options['type'], $options);				$redirected->history[] = $return;				return $redirected;			}			elseif ($options['redirected'] >= $options['redirects']) {				throw new Requests_Exception('Too many redirects', 'toomanyredirects', $return);			}		}		$return->redirects = $options['redirected'];		$options['hooks']->dispatch('requests.after_request', array(&$return, $req_headers, $req_data, $options));		return $return;	}	/**	 * Callback for `transport.internal.parse_response`	 *	 * Internal use only. Converts a raw HTTP response to a Requests_Response	 * while still executing a multiple request.	 *	 * @param string $response Full response text including headers and body (will be overwritten with Response instance)	 * @param array $request Request data as passed into {@see Requests::request_multiple()}	 * @return null `$response` is either set to a Requests_Response instance, or a Requests_Exception object	 */	public static function parse_multiple(&$response, $request) {		try {			$url = $request['url'];			$headers = $request['headers'];			$data = $request['data'];			$options = $request['options'];			$response = self::parse_response($response, $url, $headers, $data, $options);		}		catch (Requests_Exception $e) {			$response = $e;		}	}	/**	 * Decoded a chunked body as per RFC 2616	 *	 * @see https://tools.ietf.org/html/rfc2616#section-3.6.1	 * @param string $data Chunked body	 * @return string Decoded body	 */	protected static function decode_chunked($data) {		if (!preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', trim($data))) {			return $data;		}		$decoded = '';		$encoded = $data;		while (true) {			$is_chunked = (bool) preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', $encoded, $matches);			if (!$is_chunked) {				// Looks like it's not chunked after all				return $data;			}			$length = hexdec(trim($matches[1]));			if ($length === 0) {				// Ignore trailer headers				return $decoded;			}			$chunk_length = strlen($matches[0]);			$decoded .= substr($encoded, $chunk_length, $length);			$encoded = substr($encoded, $chunk_length + $length + 2);			if (trim($encoded) === '0' || empty($encoded)) {				return $decoded;			}		}		// We'll never actually get down here		// @codeCoverageIgnoreStart	}	// @codeCoverageIgnoreEnd	/**	 * Convert a key => value array to a 'key: value' array for headers	 *	 * @param array $array Dictionary of header values	 * @return array List of headers	 */	public static function flatten($array) {		$return = array();		foreach ($array as $key => $value) {			$return[] = sprintf('%s: %s', $key, $value);		}		return $return;	}	/**	 * Convert a key => value array to a 'key: value' array for headers	 *	 * @codeCoverageIgnore	 * @deprecated Misspelling of {@see Requests::flatten}	 * @param array $array Dictionary of header values	 * @return array List of headers	 */	public static function flattern($array) {		return self::flatten($array);	}	/**	 * Decompress an encoded body	 *	 * Implements gzip, compress and deflate. Guesses which it is by attempting	 * to decode.	 *	 * @param string $data Compressed data in one of the above formats	 * @return string Decompressed string	 */	public static function decompress($data) {		if (substr($data, 0, 2) !== "\x1f\x8b" && substr($data, 0, 2) !== "\x78\x9c") {			// Not actually compressed. Probably cURL ruining this for us.			return $data;		}		if (function_exists('gzdecode') && ($decoded = @gzdecode($data)) !== false) {			return $decoded;		}		elseif (function_exists('gzinflate') && ($decoded = @gzinflate($data)) !== false) {			return $decoded;		}		elseif (($decoded = self::compatible_gzinflate($data)) !== false) {			return $decoded;		}		elseif (function_exists('gzuncompress') && ($decoded = @gzuncompress($data)) !== false) {			return $decoded;		}		return $data;	}	/**	 * Decompression of deflated string while staying compatible with the majority of servers.	 *	 * Certain Servers will return deflated data with headers which PHP's gzinflate()	 * function cannot handle out of the box. The following function has been created from	 * various snippets on the gzinflate() PHP documentation.	 *	 * Warning: Magic numbers within. Due to the potential different formats that the compressed	 * data may be returned in, some "magic offsets" are needed to ensure proper decompression	 * takes place. For a simple progmatic way to determine the magic offset in use, see:	 * https://core.trac.wordpress.org/ticket/18273	 *	 * @since 2.8.1	 * @link https://core.trac.wordpress.org/ticket/18273	 * @link https://secure.php.net/manual/en/function.gzinflate.php#70875	 * @link https://secure.php.net/manual/en/function.gzinflate.php#77336	 *	 * @param string $gzData String to decompress.	 * @return string|bool False on failure.	 */	public static function compatible_gzinflate($gzData) {		// Compressed data might contain a full zlib header, if so strip it for		// gzinflate()		if (substr($gzData, 0, 3) == "\x1f\x8b\x08") {			$i = 10;			$flg = ord(substr($gzData, 3, 1));			if ($flg > 0) {				if ($flg & 4) {					list($xlen) = unpack('v', substr($gzData, $i, 2));					$i = $i + 2 + $xlen;				}				if ($flg & 8) {					$i = strpos($gzData, "\0", $i) + 1;				}				if ($flg & 16) {					$i = strpos($gzData, "\0", $i) + 1;				}				if ($flg & 2) {					$i = $i + 2;				}			}			$decompressed = self::compatible_gzinflate(substr($gzData, $i));			if (false !== $decompressed) {				return $decompressed;			}		}		// If the data is Huffman Encoded, we must first strip the leading 2		// byte Huffman marker for gzinflate()		// The response is Huffman coded by many compressors such as		// java.util.zip.Deflater, Ruby’s Zlib::Deflate, and .NET's		// System.IO.Compression.DeflateStream.		//		// See https://decompres.blogspot.com/ for a quick explanation of this		// data type		$huffman_encoded = false;		// low nibble of first byte should be 0x08		list(, $first_nibble)    = unpack('h', $gzData);		// First 2 bytes should be divisible by 0x1F		list(, $first_two_bytes) = unpack('n', $gzData);		if (0x08 == $first_nibble && 0 == ($first_two_bytes % 0x1F)) {			$huffman_encoded = true;		}		if ($huffman_encoded) {			if (false !== ($decompressed = @gzinflate(substr($gzData, 2)))) {				return $decompressed;			}		}		if ("\x50\x4b\x03\x04" == substr($gzData, 0, 4)) {			// ZIP file format header			// Offset 6: 2 bytes, General-purpose field			// Offset 26: 2 bytes, filename length			// Offset 28: 2 bytes, optional field length			// Offset 30: Filename field, followed by optional field, followed			// immediately by data			list(, $general_purpose_flag) = unpack('v', substr($gzData, 6, 2));			// If the file has been compressed on the fly, 0x08 bit is set of			// the general purpose field. We can use this to differentiate			// between a compressed document, and a ZIP file			$zip_compressed_on_the_fly = (0x08 == (0x08 & $general_purpose_flag));			if (!$zip_compressed_on_the_fly) {				// Don't attempt to decode a compressed zip file				return $gzData;			}			// Determine the first byte of data, based on the above ZIP header			// offsets:			$first_file_start = array_sum(unpack('v2', substr($gzData, 26, 4)));			if (false !== ($decompressed = @gzinflate(substr($gzData, 30 + $first_file_start)))) {				return $decompressed;			}			return false;		}		// Finally fall back to straight gzinflate		if (false !== ($decompressed = @gzinflate($gzData))) {			return $decompressed;		}		// Fallback for all above failing, not expected, but included for		// debugging and preventing regressions and to track stats		if (false !== ($decompressed = @gzinflate(substr($gzData, 2)))) {			return $decompressed;		}		return false;	}	public static function match_domain($host, $reference) {		// Check for a direct match		if ($host === $reference) {			return true;		}		// Calculate the valid wildcard match if the host is not an IP address		// Also validates that the host has 3 parts or more, as per Firefox's		// ruleset.		$parts = explode('.', $host);		if (ip2long($host) === false && count($parts) >= 3) {			$parts[0] = '*';			$wildcard = implode('.', $parts);			if ($wildcard === $reference) {				return true;			}		}		return false;	}}
 |