PageRenderTime 50ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/PEAR/HTTP/Request.php

https://bitbucket.org/andrewjleavitt/magestudy
PHP | 1521 lines | 736 code | 159 blank | 626 comment | 192 complexity | e409fdbe1518e11e16943f5185eccc2b MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1, GPL-2.0, WTFPL

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * Class for performing HTTP requests
  4. *
  5. * PHP versions 4 and 5
  6. *
  7. * LICENSE:
  8. *
  9. * Copyright (c) 2002-2007, Richard Heyes
  10. * All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or without
  13. * modification, are permitted provided that the following conditions
  14. * are met:
  15. *
  16. * o Redistributions of source code must retain the above copyright
  17. * notice, this list of conditions and the following disclaimer.
  18. * o Redistributions in binary form must reproduce the above copyright
  19. * notice, this list of conditions and the following disclaimer in the
  20. * documentation and/or other materials provided with the distribution.
  21. * o The names of the authors may not be used to endorse or promote
  22. * products derived from this software without specific prior written
  23. * permission.
  24. *
  25. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  26. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  27. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  28. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  29. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  30. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  31. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  32. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  33. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  34. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  35. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  36. *
  37. * @category HTTP
  38. * @package HTTP_Request
  39. * @author Richard Heyes <richard@phpguru.org>
  40. * @author Alexey Borzov <avb@php.net>
  41. * @copyright 2002-2007 Richard Heyes
  42. * @license http://opensource.org/licenses/bsd-license.php New BSD License
  43. * @version CVS: $Id: Request.php,v 1.63 2008/10/11 11:07:10 avb Exp $
  44. * @link http://pear.php.net/package/HTTP_Request/
  45. */
  46. /**
  47. * PEAR and PEAR_Error classes (for error handling)
  48. */
  49. require_once 'PEAR.php';
  50. /**
  51. * Socket class
  52. */
  53. require_once 'Net/Socket.php';
  54. /**
  55. * URL handling class
  56. */
  57. require_once 'Net/URL.php';
  58. /**#@+
  59. * Constants for HTTP request methods
  60. */
  61. define('HTTP_REQUEST_METHOD_GET', 'GET', true);
  62. define('HTTP_REQUEST_METHOD_HEAD', 'HEAD', true);
  63. define('HTTP_REQUEST_METHOD_POST', 'POST', true);
  64. define('HTTP_REQUEST_METHOD_PUT', 'PUT', true);
  65. define('HTTP_REQUEST_METHOD_DELETE', 'DELETE', true);
  66. define('HTTP_REQUEST_METHOD_OPTIONS', 'OPTIONS', true);
  67. define('HTTP_REQUEST_METHOD_TRACE', 'TRACE', true);
  68. /**#@-*/
  69. /**#@+
  70. * Constants for HTTP request error codes
  71. */
  72. define('HTTP_REQUEST_ERROR_FILE', 1);
  73. define('HTTP_REQUEST_ERROR_URL', 2);
  74. define('HTTP_REQUEST_ERROR_PROXY', 4);
  75. define('HTTP_REQUEST_ERROR_REDIRECTS', 8);
  76. define('HTTP_REQUEST_ERROR_RESPONSE', 16);
  77. define('HTTP_REQUEST_ERROR_GZIP_METHOD', 32);
  78. define('HTTP_REQUEST_ERROR_GZIP_READ', 64);
  79. define('HTTP_REQUEST_ERROR_GZIP_DATA', 128);
  80. define('HTTP_REQUEST_ERROR_GZIP_CRC', 256);
  81. /**#@-*/
  82. /**#@+
  83. * Constants for HTTP protocol versions
  84. */
  85. define('HTTP_REQUEST_HTTP_VER_1_0', '1.0', true);
  86. define('HTTP_REQUEST_HTTP_VER_1_1', '1.1', true);
  87. /**#@-*/
  88. if (extension_loaded('mbstring') && (2 & ini_get('mbstring.func_overload'))) {
  89. /**
  90. * Whether string functions are overloaded by their mbstring equivalents
  91. */
  92. define('HTTP_REQUEST_MBSTRING', true);
  93. } else {
  94. /**
  95. * @ignore
  96. */
  97. define('HTTP_REQUEST_MBSTRING', false);
  98. }
  99. /**
  100. * Class for performing HTTP requests
  101. *
  102. * Simple example (fetches yahoo.com and displays it):
  103. * <code>
  104. * $a = &new HTTP_Request('http://www.yahoo.com/');
  105. * $a->sendRequest();
  106. * echo $a->getResponseBody();
  107. * </code>
  108. *
  109. * @category HTTP
  110. * @package HTTP_Request
  111. * @author Richard Heyes <richard@phpguru.org>
  112. * @author Alexey Borzov <avb@php.net>
  113. * @version Release: 1.4.4
  114. */
  115. class HTTP_Request
  116. {
  117. /**#@+
  118. * @access private
  119. */
  120. /**
  121. * Instance of Net_URL
  122. * @var Net_URL
  123. */
  124. var $_url;
  125. /**
  126. * Type of request
  127. * @var string
  128. */
  129. var $_method;
  130. /**
  131. * HTTP Version
  132. * @var string
  133. */
  134. var $_http;
  135. /**
  136. * Request headers
  137. * @var array
  138. */
  139. var $_requestHeaders;
  140. /**
  141. * Basic Auth Username
  142. * @var string
  143. */
  144. var $_user;
  145. /**
  146. * Basic Auth Password
  147. * @var string
  148. */
  149. var $_pass;
  150. /**
  151. * Socket object
  152. * @var Net_Socket
  153. */
  154. var $_sock;
  155. /**
  156. * Proxy server
  157. * @var string
  158. */
  159. var $_proxy_host;
  160. /**
  161. * Proxy port
  162. * @var integer
  163. */
  164. var $_proxy_port;
  165. /**
  166. * Proxy username
  167. * @var string
  168. */
  169. var $_proxy_user;
  170. /**
  171. * Proxy password
  172. * @var string
  173. */
  174. var $_proxy_pass;
  175. /**
  176. * Post data
  177. * @var array
  178. */
  179. var $_postData;
  180. /**
  181. * Request body
  182. * @var string
  183. */
  184. var $_body;
  185. /**
  186. * A list of methods that MUST NOT have a request body, per RFC 2616
  187. * @var array
  188. */
  189. var $_bodyDisallowed = array('TRACE');
  190. /**
  191. * Methods having defined semantics for request body
  192. *
  193. * Content-Length header (indicating that the body follows, section 4.3 of
  194. * RFC 2616) will be sent for these methods even if no body was added
  195. *
  196. * @var array
  197. */
  198. var $_bodyRequired = array('POST', 'PUT');
  199. /**
  200. * Files to post
  201. * @var array
  202. */
  203. var $_postFiles = array();
  204. /**
  205. * Connection timeout.
  206. * @var float
  207. */
  208. var $_timeout;
  209. /**
  210. * HTTP_Response object
  211. * @var HTTP_Response
  212. */
  213. var $_response;
  214. /**
  215. * Whether to allow redirects
  216. * @var boolean
  217. */
  218. var $_allowRedirects;
  219. /**
  220. * Maximum redirects allowed
  221. * @var integer
  222. */
  223. var $_maxRedirects;
  224. /**
  225. * Current number of redirects
  226. * @var integer
  227. */
  228. var $_redirects;
  229. /**
  230. * Whether to append brackets [] to array variables
  231. * @var bool
  232. */
  233. var $_useBrackets = true;
  234. /**
  235. * Attached listeners
  236. * @var array
  237. */
  238. var $_listeners = array();
  239. /**
  240. * Whether to save response body in response object property
  241. * @var bool
  242. */
  243. var $_saveBody = true;
  244. /**
  245. * Timeout for reading from socket (array(seconds, microseconds))
  246. * @var array
  247. */
  248. var $_readTimeout = null;
  249. /**
  250. * Options to pass to Net_Socket::connect. See stream_context_create
  251. * @var array
  252. */
  253. var $_socketOptions = null;
  254. /**#@-*/
  255. /**
  256. * Constructor
  257. *
  258. * Sets up the object
  259. * @param string The url to fetch/access
  260. * @param array Associative array of parameters which can have the following keys:
  261. * <ul>
  262. * <li>method - Method to use, GET, POST etc (string)</li>
  263. * <li>http - HTTP Version to use, 1.0 or 1.1 (string)</li>
  264. * <li>user - Basic Auth username (string)</li>
  265. * <li>pass - Basic Auth password (string)</li>
  266. * <li>proxy_host - Proxy server host (string)</li>
  267. * <li>proxy_port - Proxy server port (integer)</li>
  268. * <li>proxy_user - Proxy auth username (string)</li>
  269. * <li>proxy_pass - Proxy auth password (string)</li>
  270. * <li>timeout - Connection timeout in seconds (float)</li>
  271. * <li>allowRedirects - Whether to follow redirects or not (bool)</li>
  272. * <li>maxRedirects - Max number of redirects to follow (integer)</li>
  273. * <li>useBrackets - Whether to append [] to array variable names (bool)</li>
  274. * <li>saveBody - Whether to save response body in response object property (bool)</li>
  275. * <li>readTimeout - Timeout for reading / writing data over the socket (array (seconds, microseconds))</li>
  276. * <li>socketOptions - Options to pass to Net_Socket object (array)</li>
  277. * </ul>
  278. * @access public
  279. */
  280. function HTTP_Request($url = '', $params = array())
  281. {
  282. $this->_method = HTTP_REQUEST_METHOD_GET;
  283. $this->_http = HTTP_REQUEST_HTTP_VER_1_1;
  284. $this->_requestHeaders = array();
  285. $this->_postData = array();
  286. $this->_body = null;
  287. $this->_user = null;
  288. $this->_pass = null;
  289. $this->_proxy_host = null;
  290. $this->_proxy_port = null;
  291. $this->_proxy_user = null;
  292. $this->_proxy_pass = null;
  293. $this->_allowRedirects = false;
  294. $this->_maxRedirects = 3;
  295. $this->_redirects = 0;
  296. $this->_timeout = null;
  297. $this->_response = null;
  298. foreach ($params as $key => $value) {
  299. $this->{'_' . $key} = $value;
  300. }
  301. if (!empty($url)) {
  302. $this->setURL($url);
  303. }
  304. // Default useragent
  305. $this->addHeader('User-Agent', 'PEAR HTTP_Request class ( http://pear.php.net/ )');
  306. // We don't do keep-alives by default
  307. $this->addHeader('Connection', 'close');
  308. // Basic authentication
  309. if (!empty($this->_user)) {
  310. $this->addHeader('Authorization', 'Basic ' . base64_encode($this->_user . ':' . $this->_pass));
  311. }
  312. // Proxy authentication (see bug #5913)
  313. if (!empty($this->_proxy_user)) {
  314. $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($this->_proxy_user . ':' . $this->_proxy_pass));
  315. }
  316. // Use gzip encoding if possible
  317. if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && extension_loaded('zlib')) {
  318. $this->addHeader('Accept-Encoding', 'gzip');
  319. }
  320. }
  321. /**
  322. * Generates a Host header for HTTP/1.1 requests
  323. *
  324. * @access private
  325. * @return string
  326. */
  327. function _generateHostHeader()
  328. {
  329. if ($this->_url->port != 80 AND strcasecmp($this->_url->protocol, 'http') == 0) {
  330. $host = $this->_url->host . ':' . $this->_url->port;
  331. } elseif ($this->_url->port != 443 AND strcasecmp($this->_url->protocol, 'https') == 0) {
  332. $host = $this->_url->host . ':' . $this->_url->port;
  333. } elseif ($this->_url->port == 443 AND strcasecmp($this->_url->protocol, 'https') == 0 AND strpos($this->_url->url, ':443') !== false) {
  334. $host = $this->_url->host . ':' . $this->_url->port;
  335. } else {
  336. $host = $this->_url->host;
  337. }
  338. return $host;
  339. }
  340. /**
  341. * Resets the object to its initial state (DEPRECATED).
  342. * Takes the same parameters as the constructor.
  343. *
  344. * @param string $url The url to be requested
  345. * @param array $params Associative array of parameters
  346. * (see constructor for details)
  347. * @access public
  348. * @deprecated deprecated since 1.2, call the constructor if this is necessary
  349. */
  350. function reset($url, $params = array())
  351. {
  352. $this->HTTP_Request($url, $params);
  353. }
  354. /**
  355. * Sets the URL to be requested
  356. *
  357. * @param string The url to be requested
  358. * @access public
  359. */
  360. function setURL($url)
  361. {
  362. $this->_url = new Net_URL($url, $this->_useBrackets);
  363. if (!empty($this->_url->user) || !empty($this->_url->pass)) {
  364. $this->setBasicAuth($this->_url->user, $this->_url->pass);
  365. }
  366. if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http) {
  367. $this->addHeader('Host', $this->_generateHostHeader());
  368. }
  369. // set '/' instead of empty path rather than check later (see bug #8662)
  370. if (empty($this->_url->path)) {
  371. $this->_url->path = '/';
  372. }
  373. }
  374. /**
  375. * Returns the current request URL
  376. *
  377. * @return string Current request URL
  378. * @access public
  379. */
  380. function getUrl()
  381. {
  382. return empty($this->_url)? '': $this->_url->getUrl();
  383. }
  384. /**
  385. * Sets a proxy to be used
  386. *
  387. * @param string Proxy host
  388. * @param int Proxy port
  389. * @param string Proxy username
  390. * @param string Proxy password
  391. * @access public
  392. */
  393. function setProxy($host, $port = 8080, $user = null, $pass = null)
  394. {
  395. $this->_proxy_host = $host;
  396. $this->_proxy_port = $port;
  397. $this->_proxy_user = $user;
  398. $this->_proxy_pass = $pass;
  399. if (!empty($user)) {
  400. $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($user . ':' . $pass));
  401. }
  402. }
  403. /**
  404. * Sets basic authentication parameters
  405. *
  406. * @param string Username
  407. * @param string Password
  408. */
  409. function setBasicAuth($user, $pass)
  410. {
  411. $this->_user = $user;
  412. $this->_pass = $pass;
  413. $this->addHeader('Authorization', 'Basic ' . base64_encode($user . ':' . $pass));
  414. }
  415. /**
  416. * Sets the method to be used, GET, POST etc.
  417. *
  418. * @param string Method to use. Use the defined constants for this
  419. * @access public
  420. */
  421. function setMethod($method)
  422. {
  423. $this->_method = $method;
  424. }
  425. /**
  426. * Sets the HTTP version to use, 1.0 or 1.1
  427. *
  428. * @param string Version to use. Use the defined constants for this
  429. * @access public
  430. */
  431. function setHttpVer($http)
  432. {
  433. $this->_http = $http;
  434. }
  435. /**
  436. * Adds a request header
  437. *
  438. * @param string Header name
  439. * @param string Header value
  440. * @access public
  441. */
  442. function addHeader($name, $value)
  443. {
  444. $this->_requestHeaders[strtolower($name)] = $value;
  445. }
  446. /**
  447. * Removes a request header
  448. *
  449. * @param string Header name to remove
  450. * @access public
  451. */
  452. function removeHeader($name)
  453. {
  454. if (isset($this->_requestHeaders[strtolower($name)])) {
  455. unset($this->_requestHeaders[strtolower($name)]);
  456. }
  457. }
  458. /**
  459. * Adds a querystring parameter
  460. *
  461. * @param string Querystring parameter name
  462. * @param string Querystring parameter value
  463. * @param bool Whether the value is already urlencoded or not, default = not
  464. * @access public
  465. */
  466. function addQueryString($name, $value, $preencoded = false)
  467. {
  468. $this->_url->addQueryString($name, $value, $preencoded);
  469. }
  470. /**
  471. * Sets the querystring to literally what you supply
  472. *
  473. * @param string The querystring data. Should be of the format foo=bar&x=y etc
  474. * @param bool Whether data is already urlencoded or not, default = already encoded
  475. * @access public
  476. */
  477. function addRawQueryString($querystring, $preencoded = true)
  478. {
  479. $this->_url->addRawQueryString($querystring, $preencoded);
  480. }
  481. /**
  482. * Adds postdata items
  483. *
  484. * @param string Post data name
  485. * @param string Post data value
  486. * @param bool Whether data is already urlencoded or not, default = not
  487. * @access public
  488. */
  489. function addPostData($name, $value, $preencoded = false)
  490. {
  491. if ($preencoded) {
  492. $this->_postData[$name] = $value;
  493. } else {
  494. $this->_postData[$name] = $this->_arrayMapRecursive('urlencode', $value);
  495. }
  496. }
  497. /**
  498. * Recursively applies the callback function to the value
  499. *
  500. * @param mixed Callback function
  501. * @param mixed Value to process
  502. * @access private
  503. * @return mixed Processed value
  504. */
  505. function _arrayMapRecursive($callback, $value)
  506. {
  507. if (!is_array($value)) {
  508. return call_user_func($callback, $value);
  509. } else {
  510. $map = array();
  511. foreach ($value as $k => $v) {
  512. $map[$k] = $this->_arrayMapRecursive($callback, $v);
  513. }
  514. return $map;
  515. }
  516. }
  517. /**
  518. * Adds a file to form-based file upload
  519. *
  520. * Used to emulate file upload via a HTML form. The method also sets
  521. * Content-Type of HTTP request to 'multipart/form-data'.
  522. *
  523. * If you just want to send the contents of a file as the body of HTTP
  524. * request you should use setBody() method.
  525. *
  526. * @access public
  527. * @param string name of file-upload field
  528. * @param mixed file name(s)
  529. * @param mixed content-type(s) of file(s) being uploaded
  530. * @return bool true on success
  531. * @throws PEAR_Error
  532. */
  533. function addFile($inputName, $fileName, $contentType = 'application/octet-stream')
  534. {
  535. if (!is_array($fileName) && !is_readable($fileName)) {
  536. return PEAR::raiseError("File '{$fileName}' is not readable", HTTP_REQUEST_ERROR_FILE);
  537. } elseif (is_array($fileName)) {
  538. foreach ($fileName as $name) {
  539. if (!is_readable($name)) {
  540. return PEAR::raiseError("File '{$name}' is not readable", HTTP_REQUEST_ERROR_FILE);
  541. }
  542. }
  543. }
  544. $this->addHeader('Content-Type', 'multipart/form-data');
  545. $this->_postFiles[$inputName] = array(
  546. 'name' => $fileName,
  547. 'type' => $contentType
  548. );
  549. return true;
  550. }
  551. /**
  552. * Adds raw postdata (DEPRECATED)
  553. *
  554. * @param string The data
  555. * @param bool Whether data is preencoded or not, default = already encoded
  556. * @access public
  557. * @deprecated deprecated since 1.3.0, method setBody() should be used instead
  558. */
  559. function addRawPostData($postdata, $preencoded = true)
  560. {
  561. $this->_body = $preencoded ? $postdata : urlencode($postdata);
  562. }
  563. /**
  564. * Sets the request body (for POST, PUT and similar requests)
  565. *
  566. * @param string Request body
  567. * @access public
  568. */
  569. function setBody($body)
  570. {
  571. $this->_body = $body;
  572. }
  573. /**
  574. * Clears any postdata that has been added (DEPRECATED).
  575. *
  576. * Useful for multiple request scenarios.
  577. *
  578. * @access public
  579. * @deprecated deprecated since 1.2
  580. */
  581. function clearPostData()
  582. {
  583. $this->_postData = null;
  584. }
  585. /**
  586. * Appends a cookie to "Cookie:" header
  587. *
  588. * @param string $name cookie name
  589. * @param string $value cookie value
  590. * @access public
  591. */
  592. function addCookie($name, $value)
  593. {
  594. $cookies = isset($this->_requestHeaders['cookie']) ? $this->_requestHeaders['cookie']. '; ' : '';
  595. $this->addHeader('Cookie', $cookies . $name . '=' . $value);
  596. }
  597. /**
  598. * Clears any cookies that have been added (DEPRECATED).
  599. *
  600. * Useful for multiple request scenarios
  601. *
  602. * @access public
  603. * @deprecated deprecated since 1.2
  604. */
  605. function clearCookies()
  606. {
  607. $this->removeHeader('Cookie');
  608. }
  609. /**
  610. * Sends the request
  611. *
  612. * @access public
  613. * @param bool Whether to store response body in Response object property,
  614. * set this to false if downloading a LARGE file and using a Listener
  615. * @return mixed PEAR error on error, true otherwise
  616. */
  617. function sendRequest($saveBody = true)
  618. {
  619. if (!$this->_url instanceof Net_Url) {
  620. return PEAR::raiseError('No URL given', HTTP_REQUEST_ERROR_URL);
  621. }
  622. $host = isset($this->_proxy_host) ? $this->_proxy_host : $this->_url->host;
  623. $port = isset($this->_proxy_port) ? $this->_proxy_port : $this->_url->port;
  624. if (strcasecmp($this->_url->protocol, 'https') == 0) {
  625. // Bug #14127, don't try connecting to HTTPS sites without OpenSSL
  626. if (version_compare(PHP_VERSION, '4.3.0', '<') || !extension_loaded('openssl')) {
  627. return PEAR::raiseError('Need PHP 4.3.0 or later with OpenSSL support for https:// requests',
  628. HTTP_REQUEST_ERROR_URL);
  629. } elseif (isset($this->_proxy_host)) {
  630. return PEAR::raiseError('HTTPS proxies are not supported', HTTP_REQUEST_ERROR_PROXY);
  631. }
  632. $host = 'ssl://' . $host;
  633. }
  634. // magic quotes may fuck up file uploads and chunked response processing
  635. $magicQuotes = ini_get('magic_quotes_runtime');
  636. ini_set('magic_quotes_runtime', false);
  637. // RFC 2068, section 19.7.1: A client MUST NOT send the Keep-Alive
  638. // connection token to a proxy server...
  639. if (isset($this->_proxy_host) && !empty($this->_requestHeaders['connection']) &&
  640. 'Keep-Alive' == $this->_requestHeaders['connection'])
  641. {
  642. $this->removeHeader('connection');
  643. }
  644. $keepAlive = (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && empty($this->_requestHeaders['connection'])) ||
  645. (!empty($this->_requestHeaders['connection']) && 'Keep-Alive' == $this->_requestHeaders['connection']);
  646. $sockets = &PEAR::getStaticProperty('HTTP_Request', 'sockets');
  647. $sockKey = $host . ':' . $port;
  648. unset($this->_sock);
  649. // There is a connected socket in the "static" property?
  650. if ($keepAlive && !empty($sockets[$sockKey]) &&
  651. !empty($sockets[$sockKey]->fp))
  652. {
  653. $this->_sock =& $sockets[$sockKey];
  654. $err = null;
  655. } else {
  656. $this->_notify('connect');
  657. $this->_sock = new Net_Socket();
  658. $err = $this->_sock->connect($host, $port, null, $this->_timeout, $this->_socketOptions);
  659. }
  660. PEAR::isError($err) or $err = $this->_sock->write($this->_buildRequest());
  661. if (!PEAR::isError($err)) {
  662. if (!empty($this->_readTimeout)) {
  663. $this->_sock->setTimeout($this->_readTimeout[0], $this->_readTimeout[1]);
  664. }
  665. $this->_notify('sentRequest');
  666. // Read the response
  667. $this->_response = new HTTP_Response($this->_sock, $this->_listeners);
  668. $err = $this->_response->process(
  669. $this->_saveBody && $saveBody,
  670. HTTP_REQUEST_METHOD_HEAD != $this->_method
  671. );
  672. if ($keepAlive) {
  673. $keepAlive = (isset($this->_response->_headers['content-length'])
  674. || (isset($this->_response->_headers['transfer-encoding'])
  675. && strtolower($this->_response->_headers['transfer-encoding']) == 'chunked'));
  676. if ($keepAlive) {
  677. if (isset($this->_response->_headers['connection'])) {
  678. $keepAlive = strtolower($this->_response->_headers['connection']) == 'keep-alive';
  679. } else {
  680. $keepAlive = 'HTTP/'.HTTP_REQUEST_HTTP_VER_1_1 == $this->_response->_protocol;
  681. }
  682. }
  683. }
  684. }
  685. ini_set('magic_quotes_runtime', $magicQuotes);
  686. if (PEAR::isError($err)) {
  687. return $err;
  688. }
  689. if (!$keepAlive) {
  690. $this->disconnect();
  691. // Store the connected socket in "static" property
  692. } elseif (empty($sockets[$sockKey]) || empty($sockets[$sockKey]->fp)) {
  693. $sockets[$sockKey] =& $this->_sock;
  694. }
  695. // Check for redirection
  696. if ( $this->_allowRedirects
  697. AND $this->_redirects <= $this->_maxRedirects
  698. AND $this->getResponseCode() > 300
  699. AND $this->getResponseCode() < 399
  700. AND !empty($this->_response->_headers['location'])) {
  701. $redirect = $this->_response->_headers['location'];
  702. // Absolute URL
  703. if (preg_match('/^https?:\/\//i', $redirect)) {
  704. $this->_url = new Net_URL($redirect);
  705. $this->addHeader('Host', $this->_generateHostHeader());
  706. // Absolute path
  707. } elseif ($redirect{0} == '/') {
  708. $this->_url->path = $redirect;
  709. // Relative path
  710. } elseif (substr($redirect, 0, 3) == '../' OR substr($redirect, 0, 2) == './') {
  711. if (substr($this->_url->path, -1) == '/') {
  712. $redirect = $this->_url->path . $redirect;
  713. } else {
  714. $redirect = dirname($this->_url->path) . '/' . $redirect;
  715. }
  716. $redirect = Net_URL::resolvePath($redirect);
  717. $this->_url->path = $redirect;
  718. // Filename, no path
  719. } else {
  720. if (substr($this->_url->path, -1) == '/') {
  721. $redirect = $this->_url->path . $redirect;
  722. } else {
  723. $redirect = dirname($this->_url->path) . '/' . $redirect;
  724. }
  725. $this->_url->path = $redirect;
  726. }
  727. $this->_redirects++;
  728. return $this->sendRequest($saveBody);
  729. // Too many redirects
  730. } elseif ($this->_allowRedirects AND $this->_redirects > $this->_maxRedirects) {
  731. return PEAR::raiseError('Too many redirects', HTTP_REQUEST_ERROR_REDIRECTS);
  732. }
  733. return true;
  734. }
  735. /**
  736. * Disconnect the socket, if connected. Only useful if using Keep-Alive.
  737. *
  738. * @access public
  739. */
  740. function disconnect()
  741. {
  742. if (!empty($this->_sock) && !empty($this->_sock->fp)) {
  743. $this->_notify('disconnect');
  744. $this->_sock->disconnect();
  745. }
  746. }
  747. /**
  748. * Returns the response code
  749. *
  750. * @access public
  751. * @return mixed Response code, false if not set
  752. */
  753. function getResponseCode()
  754. {
  755. return isset($this->_response->_code) ? $this->_response->_code : false;
  756. }
  757. /**
  758. * Returns the response reason phrase
  759. *
  760. * @access public
  761. * @return mixed Response reason phrase, false if not set
  762. */
  763. function getResponseReason()
  764. {
  765. return isset($this->_response->_reason) ? $this->_response->_reason : false;
  766. }
  767. /**
  768. * Returns either the named header or all if no name given
  769. *
  770. * @access public
  771. * @param string The header name to return, do not set to get all headers
  772. * @return mixed either the value of $headername (false if header is not present)
  773. * or an array of all headers
  774. */
  775. function getResponseHeader($headername = null)
  776. {
  777. if (!isset($headername)) {
  778. return isset($this->_response->_headers)? $this->_response->_headers: array();
  779. } else {
  780. $headername = strtolower($headername);
  781. return isset($this->_response->_headers[$headername]) ? $this->_response->_headers[$headername] : false;
  782. }
  783. }
  784. /**
  785. * Returns the body of the response
  786. *
  787. * @access public
  788. * @return mixed response body, false if not set
  789. */
  790. function getResponseBody()
  791. {
  792. return isset($this->_response->_body) ? $this->_response->_body : false;
  793. }
  794. /**
  795. * Returns cookies set in response
  796. *
  797. * @access public
  798. * @return mixed array of response cookies, false if none are present
  799. */
  800. function getResponseCookies()
  801. {
  802. return isset($this->_response->_cookies) ? $this->_response->_cookies : false;
  803. }
  804. /**
  805. * Builds the request string
  806. *
  807. * @access private
  808. * @return string The request string
  809. */
  810. function _buildRequest()
  811. {
  812. $separator = ini_get('arg_separator.output');
  813. ini_set('arg_separator.output', '&');
  814. $querystring = ($querystring = $this->_url->getQueryString()) ? '?' . $querystring : '';
  815. ini_set('arg_separator.output', $separator);
  816. $host = isset($this->_proxy_host) ? $this->_url->protocol . '://' . $this->_url->host : '';
  817. $port = (isset($this->_proxy_host) AND $this->_url->port != 80) ? ':' . $this->_url->port : '';
  818. $path = $this->_url->path . $querystring;
  819. $url = $host . $port . $path;
  820. if (!strlen($url)) {
  821. $url = '/';
  822. }
  823. $request = $this->_method . ' ' . $url . ' HTTP/' . $this->_http . "\r\n";
  824. if (in_array($this->_method, $this->_bodyDisallowed) ||
  825. (0 == strlen($this->_body) && (HTTP_REQUEST_METHOD_POST != $this->_method ||
  826. (empty($this->_postData) && empty($this->_postFiles)))))
  827. {
  828. $this->removeHeader('Content-Type');
  829. } else {
  830. if (empty($this->_requestHeaders['content-type'])) {
  831. // Add default content-type
  832. $this->addHeader('Content-Type', 'application/x-www-form-urlencoded');
  833. } elseif ('multipart/form-data' == $this->_requestHeaders['content-type']) {
  834. $boundary = 'HTTP_Request_' . md5(uniqid('request') . microtime());
  835. $this->addHeader('Content-Type', 'multipart/form-data; boundary=' . $boundary);
  836. }
  837. }
  838. // Request Headers
  839. if (!empty($this->_requestHeaders)) {
  840. foreach ($this->_requestHeaders as $name => $value) {
  841. $canonicalName = implode('-', array_map('ucfirst', explode('-', $name)));
  842. $request .= $canonicalName . ': ' . $value . "\r\n";
  843. }
  844. }
  845. // Method does not allow a body, simply add a final CRLF
  846. if (in_array($this->_method, $this->_bodyDisallowed)) {
  847. $request .= "\r\n";
  848. // Post data if it's an array
  849. } elseif (HTTP_REQUEST_METHOD_POST == $this->_method &&
  850. (!empty($this->_postData) || !empty($this->_postFiles))) {
  851. // "normal" POST request
  852. if (!isset($boundary)) {
  853. $postdata = implode('&', array_map(
  854. create_function('$a', 'return $a[0] . \'=\' . $a[1];'),
  855. $this->_flattenArray('', $this->_postData)
  856. ));
  857. // multipart request, probably with file uploads
  858. } else {
  859. $postdata = '';
  860. if (!empty($this->_postData)) {
  861. $flatData = $this->_flattenArray('', $this->_postData);
  862. foreach ($flatData as $item) {
  863. $postdata .= '--' . $boundary . "\r\n";
  864. $postdata .= 'Content-Disposition: form-data; name="' . $item[0] . '"';
  865. $postdata .= "\r\n\r\n" . urldecode($item[1]) . "\r\n";
  866. }
  867. }
  868. foreach ($this->_postFiles as $name => $value) {
  869. if (is_array($value['name'])) {
  870. $varname = $name . ($this->_useBrackets? '[]': '');
  871. } else {
  872. $varname = $name;
  873. $value['name'] = array($value['name']);
  874. }
  875. foreach ($value['name'] as $key => $filename) {
  876. $fp = fopen($filename, 'r');
  877. $basename = basename($filename);
  878. $type = is_array($value['type'])? @$value['type'][$key]: $value['type'];
  879. $postdata .= '--' . $boundary . "\r\n";
  880. $postdata .= 'Content-Disposition: form-data; name="' . $varname . '"; filename="' . $basename . '"';
  881. $postdata .= "\r\nContent-Type: " . $type;
  882. $postdata .= "\r\n\r\n" . fread($fp, filesize($filename)) . "\r\n";
  883. fclose($fp);
  884. }
  885. }
  886. $postdata .= '--' . $boundary . "--\r\n";
  887. }
  888. $request .= 'Content-Length: ' .
  889. (HTTP_REQUEST_MBSTRING? mb_strlen($postdata, 'iso-8859-1'): strlen($postdata)) .
  890. "\r\n\r\n";
  891. $request .= $postdata;
  892. // Explicitly set request body
  893. } elseif (0 < strlen($this->_body)) {
  894. $request .= 'Content-Length: ' .
  895. (HTTP_REQUEST_MBSTRING? mb_strlen($this->_body, 'iso-8859-1'): strlen($this->_body)) .
  896. "\r\n\r\n";
  897. $request .= $this->_body;
  898. // No body: send a Content-Length header nonetheless (request #12900),
  899. // but do that only for methods that require a body (bug #14740)
  900. } else {
  901. if (in_array($this->_method, $this->_bodyRequired)) {
  902. $request .= "Content-Length: 0\r\n";
  903. }
  904. $request .= "\r\n";
  905. }
  906. return $request;
  907. }
  908. /**
  909. * Helper function to change the (probably multidimensional) associative array
  910. * into the simple one.
  911. *
  912. * @param string name for item
  913. * @param mixed item's values
  914. * @return array array with the following items: array('item name', 'item value');
  915. * @access private
  916. */
  917. function _flattenArray($name, $values)
  918. {
  919. if (!is_array($values)) {
  920. return array(array($name, $values));
  921. } else {
  922. $ret = array();
  923. foreach ($values as $k => $v) {
  924. if (empty($name)) {
  925. $newName = $k;
  926. } elseif ($this->_useBrackets) {
  927. $newName = $name . '[' . $k . ']';
  928. } else {
  929. $newName = $name;
  930. }
  931. $ret = array_merge($ret, $this->_flattenArray($newName, $v));
  932. }
  933. return $ret;
  934. }
  935. }
  936. /**
  937. * Adds a Listener to the list of listeners that are notified of
  938. * the object's events
  939. *
  940. * Events sent by HTTP_Request object
  941. * - 'connect': on connection to server
  942. * - 'sentRequest': after the request was sent
  943. * - 'disconnect': on disconnection from server
  944. *
  945. * Events sent by HTTP_Response object
  946. * - 'gotHeaders': after receiving response headers (headers are passed in $data)
  947. * - 'tick': on receiving a part of response body (the part is passed in $data)
  948. * - 'gzTick': on receiving a gzip-encoded part of response body (ditto)
  949. * - 'gotBody': after receiving the response body (passes the decoded body in $data if it was gzipped)
  950. *
  951. * @param HTTP_Request_Listener listener to attach
  952. * @return boolean whether the listener was successfully attached
  953. * @access public
  954. */
  955. function attach(&$listener)
  956. {
  957. if (!is_a($listener, 'HTTP_Request_Listener')) {
  958. return false;
  959. }
  960. $this->_listeners[$listener->getId()] =& $listener;
  961. return true;
  962. }
  963. /**
  964. * Removes a Listener from the list of listeners
  965. *
  966. * @param HTTP_Request_Listener listener to detach
  967. * @return boolean whether the listener was successfully detached
  968. * @access public
  969. */
  970. function detach(&$listener)
  971. {
  972. if (!is_a($listener, 'HTTP_Request_Listener') ||
  973. !isset($this->_listeners[$listener->getId()])) {
  974. return false;
  975. }
  976. unset($this->_listeners[$listener->getId()]);
  977. return true;
  978. }
  979. /**
  980. * Notifies all registered listeners of an event.
  981. *
  982. * @param string Event name
  983. * @param mixed Additional data
  984. * @access private
  985. * @see HTTP_Request::attach()
  986. */
  987. function _notify($event, $data = null)
  988. {
  989. foreach (array_keys($this->_listeners) as $id) {
  990. $this->_listeners[$id]->update($this, $event, $data);
  991. }
  992. }
  993. }
  994. /**
  995. * Response class to complement the Request class
  996. *
  997. * @category HTTP
  998. * @package HTTP_Request
  999. * @author Richard Heyes <richard@phpguru.org>
  1000. * @author Alexey Borzov <avb@php.net>
  1001. * @version Release: 1.4.4
  1002. */
  1003. class HTTP_Response
  1004. {
  1005. /**
  1006. * Socket object
  1007. * @var Net_Socket
  1008. */
  1009. var $_sock;
  1010. /**
  1011. * Protocol
  1012. * @var string
  1013. */
  1014. var $_protocol;
  1015. /**
  1016. * Return code
  1017. * @var string
  1018. */
  1019. var $_code;
  1020. /**
  1021. * Response reason phrase
  1022. * @var string
  1023. */
  1024. var $_reason;
  1025. /**
  1026. * Response headers
  1027. * @var array
  1028. */
  1029. var $_headers;
  1030. /**
  1031. * Cookies set in response
  1032. * @var array
  1033. */
  1034. var $_cookies;
  1035. /**
  1036. * Response body
  1037. * @var string
  1038. */
  1039. var $_body = '';
  1040. /**
  1041. * Used by _readChunked(): remaining length of the current chunk
  1042. * @var string
  1043. */
  1044. var $_chunkLength = 0;
  1045. /**
  1046. * Attached listeners
  1047. * @var array
  1048. */
  1049. var $_listeners = array();
  1050. /**
  1051. * Bytes left to read from message-body
  1052. * @var null|int
  1053. */
  1054. var $_toRead;
  1055. /**
  1056. * Constructor
  1057. *
  1058. * @param Net_Socket socket to read the response from
  1059. * @param array listeners attached to request
  1060. */
  1061. function HTTP_Response(&$sock, &$listeners)
  1062. {
  1063. $this->_sock =& $sock;
  1064. $this->_listeners =& $listeners;
  1065. }
  1066. /**
  1067. * Processes a HTTP response
  1068. *
  1069. * This extracts response code, headers, cookies and decodes body if it
  1070. * was encoded in some way
  1071. *
  1072. * @access public
  1073. * @param bool Whether to store response body in object property, set
  1074. * this to false if downloading a LARGE file and using a Listener.
  1075. * This is assumed to be true if body is gzip-encoded.
  1076. * @param bool Whether the response can actually have a message-body.
  1077. * Will be set to false for HEAD requests.
  1078. * @throws PEAR_Error
  1079. * @return mixed true on success, PEAR_Error in case of malformed response
  1080. */
  1081. function process($saveBody = true, $canHaveBody = true)
  1082. {
  1083. do {
  1084. $line = $this->_sock->readLine();
  1085. if (!preg_match('!^(HTTP/\d\.\d) (\d{3})(?: (.+))?!', $line, $s)) {
  1086. return PEAR::raiseError('Malformed response', HTTP_REQUEST_ERROR_RESPONSE);
  1087. } else {
  1088. $this->_protocol = $s[1];
  1089. $this->_code = intval($s[2]);
  1090. $this->_reason = empty($s[3])? null: $s[3];
  1091. }
  1092. while ('' !== ($header = $this->_sock->readLine())) {
  1093. $this->_processHeader($header);
  1094. }
  1095. } while (100 == $this->_code);
  1096. $this->_notify('gotHeaders', $this->_headers);
  1097. // RFC 2616, section 4.4:
  1098. // 1. Any response message which "MUST NOT" include a message-body ...
  1099. // is always terminated by the first empty line after the header fields
  1100. // 3. ... If a message is received with both a
  1101. // Transfer-Encoding header field and a Content-Length header field,
  1102. // the latter MUST be ignored.
  1103. $canHaveBody = $canHaveBody && $this->_code >= 200 &&
  1104. $this->_code != 204 && $this->_code != 304;
  1105. // If response body is present, read it and decode
  1106. $chunked = isset($this->_headers['transfer-encoding']) && ('chunked' == $this->_headers['transfer-encoding']);
  1107. $gzipped = isset($this->_headers['content-encoding']) && ('gzip' == $this->_headers['content-encoding']);
  1108. $hasBody = false;
  1109. if ($canHaveBody && ($chunked || !isset($this->_headers['content-length']) ||
  1110. 0 != $this->_headers['content-length']))
  1111. {
  1112. if ($chunked || !isset($this->_headers['content-length'])) {
  1113. $this->_toRead = null;
  1114. } else {
  1115. $this->_toRead = $this->_headers['content-length'];
  1116. }
  1117. while (!$this->_sock->eof() && (is_null($this->_toRead) || 0 < $this->_toRead)) {
  1118. if ($chunked) {
  1119. $data = $this->_readChunked();
  1120. } elseif (is_null($this->_toRead)) {
  1121. $data = $this->_sock->read(4096);
  1122. } else {
  1123. $data = $this->_sock->read(min(4096, $this->_toRead));
  1124. $this->_toRead -= HTTP_REQUEST_MBSTRING? mb_strlen($data, 'iso-8859-1'): strlen($data);
  1125. }
  1126. if ('' == $data && (!$this->_chunkLength || $this->_sock->eof())) {
  1127. break;
  1128. } else {
  1129. $hasBody = true;
  1130. if ($saveBody || $gzipped) {
  1131. $this->_body .= $data;
  1132. }
  1133. $this->_notify($gzipped? 'gzTick': 'tick', $data);
  1134. }
  1135. }
  1136. }
  1137. if ($hasBody) {
  1138. // Uncompress the body if needed
  1139. if ($gzipped) {
  1140. $body = $this->_decodeGzip($this->_body);
  1141. if (PEAR::isError($body)) {
  1142. return $body;
  1143. }
  1144. $this->_body = $body;
  1145. $this->_notify('gotBody', $this->_body);
  1146. } else {
  1147. $this->_notify('gotBody');
  1148. }
  1149. }
  1150. return true;
  1151. }
  1152. /**
  1153. * Processes the response header
  1154. *
  1155. * @access private
  1156. * @param string HTTP header
  1157. */
  1158. function _processHeader($header)
  1159. {
  1160. if (false === strpos($header, ':')) {
  1161. return;
  1162. }
  1163. list($headername, $headervalue) = explode(':', $header, 2);
  1164. $headername = strtolower($headername);
  1165. $headervalue = ltrim($headervalue);
  1166. if ('set-cookie' != $headername) {
  1167. if (isset($this->_headers[$headername])) {
  1168. $this->_headers[$headername] .= ',' . $headervalue;
  1169. } else {
  1170. $this->_headers[$headername] = $headervalue;
  1171. }
  1172. } else {
  1173. $this->_parseCookie($headervalue);
  1174. }
  1175. }
  1176. /**
  1177. * Parse a Set-Cookie header to fill $_cookies array
  1178. *
  1179. * @access private
  1180. * @param string value of Set-Cookie header
  1181. */
  1182. function _parseCookie($headervalue)
  1183. {
  1184. $cookie = array(
  1185. 'expires' => null,
  1186. 'domain' => null,
  1187. 'path' => null,
  1188. 'secure' => false
  1189. );
  1190. // Only a name=value pair
  1191. if (!strpos($headervalue, ';')) {
  1192. $pos = strpos($headervalue, '=');
  1193. $cookie['name'] = trim(substr($headervalue, 0, $pos));
  1194. $cookie['value'] = trim(substr($headervalue, $pos + 1));
  1195. // Some optional parameters are supplied
  1196. } else {
  1197. $elements = explode(';', $headervalue);
  1198. $pos = strpos($elements[0], '=');
  1199. $cookie['name'] = trim(substr($elements[0], 0, $pos));
  1200. $cookie['value'] = trim(substr($elements[0], $pos + 1));
  1201. for ($i = 1; $i < count($elements); $i++) {
  1202. if (false === strpos($elements[$i], '=')) {
  1203. $elName = trim($elements[$i]);
  1204. $elValue = null;
  1205. } else {
  1206. list ($elName, $elValue) = array_map('trim', explode('=', $elements[$i]));
  1207. }
  1208. $elName = strtolower($elName);
  1209. if ('secure' == $elName) {
  1210. $cookie['secure'] = true;
  1211. } elseif ('expires' == $elName) {
  1212. $cookie['expires'] = str_replace('"', '', $elValue);
  1213. } elseif ('path' == $elName || 'domain' == $elName) {
  1214. $cookie[$elName] = urldecode($elValue);
  1215. } else {
  1216. $cookie[$elName] = $elValue;
  1217. }
  1218. }
  1219. }
  1220. $this->_cookies[] = $cookie;
  1221. }
  1222. /**
  1223. * Read a part of response body encoded with chunked Transfer-Encoding
  1224. *
  1225. * @access private
  1226. * @return string
  1227. */
  1228. function _readChunked()
  1229. {
  1230. // at start of the next chunk?
  1231. if (0 == $this->_chunkLength) {
  1232. $line = $this->_sock->readLine();
  1233. if (preg_match('/^([0-9a-f]+)/i', $line, $matches)) {
  1234. $this->_chunkLength = hexdec($matches[1]);
  1235. // Chunk with zero length indicates the end
  1236. if (0 == $this->_chunkLength) {
  1237. $this->_sock->readLine(); // make this an eof()
  1238. return '';
  1239. }
  1240. } else {
  1241. return '';
  1242. }
  1243. }
  1244. $data = $this->_sock->read($this->_chunkLength);
  1245. $this->_chunkLength -= HTTP_REQUEST_MBSTRING? mb_strlen($data, 'iso-8859-1'): strlen($data);
  1246. if (0 == $this->_chunkLength) {
  1247. $this->_sock->readLine(); // Trailing CRLF
  1248. }
  1249. return $data;
  1250. }
  1251. /**
  1252. * Notifies all registered listeners of an event.
  1253. *
  1254. * @param string Event name
  1255. * @param mixed Additional data
  1256. * @access private
  1257. * @see HTTP_Request::_notify()
  1258. */
  1259. function _notify($event, $data = null)
  1260. {
  1261. foreach (array_keys($this->_listeners) as $id) {
  1262. $this->_listeners[$id]->update($this, $event, $data);
  1263. }
  1264. }
  1265. /**
  1266. * Decodes the message-body encoded by gzip
  1267. *
  1268. * The real decoding work is done by gzinflate() built-in function, this
  1269. * method only parses the header and checks data for compliance with
  1270. * RFC 1952
  1271. *
  1272. * @access private
  1273. * @param string gzip-encoded data
  1274. * @return string decoded data
  1275. */
  1276. function _decodeGzip($data)
  1277. {
  1278. if (HTTP_REQUEST_MBSTRING) {
  1279. $oldEncoding = mb_internal_encoding();
  1280. mb_internal_encoding('iso-8859-1');
  1281. }
  1282. $length = strlen($data);
  1283. // If it doesn't look like gzip-encoded data, don't bother
  1284. if (18 > $length || strcmp(substr($data, 0, 2), "\x1f\x8b")) {
  1285. return $data;
  1286. }
  1287. $method = ord(substr($data, 2, 1));
  1288. if (8 != $method) {
  1289. return PEAR::raiseError('_decodeGzip(): unknown compression method', HTTP_REQUEST_ERROR_GZIP_METHOD);
  1290. }
  1291. $flags = ord(substr($data, 3, 1));
  1292. if ($flags & 224) {
  1293. return PEAR::raiseError('_decodeGzip(): reserved bits are set', HTTP_REQUEST_ERROR_GZIP_DATA);
  1294. }
  1295. // header is 10 bytes minimum. may be longer, though.
  1296. $headerLength = 10;
  1297. // extra fields, need to skip 'em
  1298. if ($flags & 4) {
  1299. if ($length - $headerLength - 2 < 8) {
  1300. return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
  1301. }
  1302. $extraLength = unpack('v', substr($data, 10, 2));
  1303. if ($length - $headerLength - 2 - $extraLength[1] < 8) {
  1304. return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
  1305. }
  1306. $headerLength += $extraLength[1] + 2;
  1307. }
  1308. // file name, need to skip that
  1309. if ($flags & 8) {
  1310. if ($length - $headerLength - 1 < 8) {
  1311. return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
  1312. }
  1313. $filenameLength = strpos(substr($data, $headerLength), chr(0));
  1314. if (false === $filenameLength || $length - $headerLength - $filenameLength - 1 < 8) {
  1315. return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
  1316. }
  1317. $headerLength += $filenameLength + 1;
  1318. }
  1319. // comment, need to skip that also
  1320. if ($flags & 16) {
  1321. if ($length - $headerLength - 1 < 8) {
  1322. return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
  1323. }
  1324. $commentLength = strpos(substr($data, $headerLength), chr(0));
  1325. if (false === $commentLength || $length - $headerLength - $commentLength - 1 < 8) {
  1326. return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
  1327. }
  1328. $headerLength += $commentLength + 1;
  1329. }
  1330. // have a CRC for header. let's check
  1331. if ($flags & 1) {
  1332. if ($length - $headerLength - 2 < 8) {
  1333. return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
  1334. }
  1335. $crcReal = 0xffff & crc32(substr($data, 0, $headerLength));
  1336. $crcStored = unpack('v', substr($data, $headerLength, 2));
  1337. if ($crcReal != $crcStored[1]) {
  1338. return PEAR::raiseError('_decodeGzip(): header CRC check failed', HTTP_REQUEST_ERROR_GZIP_CRC);
  1339. }
  1340. $headerLength += 2;
  1341. }
  1342. // unpacked data CRC and size at the end of encoded data
  1343. $tmp = unpack('V2', substr($data, -8));
  1344. $dataCrc = $tmp[1];
  1345. $dataSize = $tmp[2];
  1346. // finally, call the gzinflate() function
  1347. // don't pass $dataSize to gzinflate, see bugs #13135, #14370
  1348. $unpacked = gzinflate(substr($data, $headerLength, -8));
  1349. if (false === $unpacked) {
  1350. return PEAR::raiseError('_decodeGzip(): gzinflate() call failed', HTTP_REQUEST_ERROR_GZIP_READ);

Large files files are truncated, but you can click here to view the full file