Requests.php 27 KB

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