Requests.php 29 KB

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