PageRenderTime 52ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/inc/app/siteinvoice/lib/PEAR/HTTP/Request.php

https://github.com/cbrunet/sitellite
PHP | 1143 lines | 528 code | 130 blank | 485 comment | 119 complexity | 90ca780d46b5c8d1b483232ba415ae05 MD5 | raw file
Possible License(s): Apache-2.0, GPL-2.0, GPL-3.0, LGPL-2.1
  1. <?php
  2. // +-----------------------------------------------------------------------+
  3. // | Copyright (c) 2002-2003, Richard Heyes |
  4. // | All rights reserved. |
  5. // | |
  6. // | Redistribution and use in source and binary forms, with or without |
  7. // | modification, are permitted provided that the following conditions |
  8. // | are met: |
  9. // | |
  10. // | o Redistributions of source code must retain the above copyright |
  11. // | notice, this list of conditions and the following disclaimer. |
  12. // | o Redistributions in binary form must reproduce the above copyright |
  13. // | notice, this list of conditions and the following disclaimer in the |
  14. // | documentation and/or other materials provided with the distribution.|
  15. // | o The names of the authors may not be used to endorse or promote |
  16. // | products derived from this software without specific prior written |
  17. // | permission. |
  18. // | |
  19. // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
  20. // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
  21. // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
  22. // | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
  23. // | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
  24. // | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
  25. // | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
  26. // | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
  27. // | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
  28. // | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
  29. // | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
  30. // | |
  31. // +-----------------------------------------------------------------------+
  32. // | Author: Richard Heyes <richard@phpguru.org> |
  33. // +-----------------------------------------------------------------------+
  34. //
  35. // $Id: Request.php,v 1.1 2005/07/02 21:12:30 lux Exp $
  36. //
  37. // HTTP_Request Class
  38. //
  39. // Simple example, (Fetches yahoo.com and displays it):
  40. //
  41. // $a = &new HTTP_Request('http://www.yahoo.com/');
  42. // $a->sendRequest();
  43. // echo $a->getResponseBody();
  44. //
  45. require_once 'PEAR.php';
  46. require_once 'Net/Socket.php';
  47. require_once 'Net/URL.php';
  48. define('HTTP_REQUEST_METHOD_GET', 'GET', true);
  49. define('HTTP_REQUEST_METHOD_HEAD', 'HEAD', true);
  50. define('HTTP_REQUEST_METHOD_POST', 'POST', true);
  51. define('HTTP_REQUEST_METHOD_PUT', 'PUT', true);
  52. define('HTTP_REQUEST_METHOD_DELETE', 'DELETE', true);
  53. define('HTTP_REQUEST_METHOD_OPTIONS', 'OPTIONS', true);
  54. define('HTTP_REQUEST_METHOD_TRACE', 'TRACE', true);
  55. define('HTTP_REQUEST_HTTP_VER_1_0', '1.0', true);
  56. define('HTTP_REQUEST_HTTP_VER_1_1', '1.1', true);
  57. class HTTP_Request {
  58. /**
  59. * Instance of Net_URL
  60. * @var object Net_URL
  61. */
  62. var $_url;
  63. /**
  64. * Type of request
  65. * @var string
  66. */
  67. var $_method;
  68. /**
  69. * HTTP Version
  70. * @var string
  71. */
  72. var $_http;
  73. /**
  74. * Request headers
  75. * @var array
  76. */
  77. var $_requestHeaders;
  78. /**
  79. * Basic Auth Username
  80. * @var string
  81. */
  82. var $_user;
  83. /**
  84. * Basic Auth Password
  85. * @var string
  86. */
  87. var $_pass;
  88. /**
  89. * Socket object
  90. * @var object Net_Socket
  91. */
  92. var $_sock;
  93. /**
  94. * Proxy server
  95. * @var string
  96. */
  97. var $_proxy_host;
  98. /**
  99. * Proxy port
  100. * @var integer
  101. */
  102. var $_proxy_port;
  103. /**
  104. * Proxy username
  105. * @var string
  106. */
  107. var $_proxy_user;
  108. /**
  109. * Proxy password
  110. * @var string
  111. */
  112. var $_proxy_pass;
  113. /**
  114. * Post data
  115. * @var mixed
  116. */
  117. var $_postData;
  118. /**
  119. * Files to post
  120. * @var array
  121. */
  122. var $_postFiles = array();
  123. /**
  124. * Connection timeout.
  125. * @var float
  126. */
  127. var $_timeout;
  128. /**
  129. * HTTP_Response object
  130. * @var object HTTP_Response
  131. */
  132. var $_response;
  133. /**
  134. * Whether to allow redirects
  135. * @var boolean
  136. */
  137. var $_allowRedirects;
  138. /**
  139. * Maximum redirects allowed
  140. * @var integer
  141. */
  142. var $_maxRedirects;
  143. /**
  144. * Current number of redirects
  145. * @var integer
  146. */
  147. var $_redirects;
  148. /**
  149. * Whether to append brackets [] to array variables
  150. * @var bool
  151. */
  152. var $_useBrackets = true;
  153. /**
  154. * Attached listeners
  155. * @var array
  156. */
  157. var $_listeners = array();
  158. /**
  159. * Whether to save response body in response object property
  160. * @var bool
  161. */
  162. var $_saveBody = true;
  163. /**
  164. * Timeout for reading from socket (array(seconds, microseconds))
  165. * @var array
  166. */
  167. var $_readTimeout = null;
  168. /**
  169. * Options to pass to Net_Socket::connect. See stream_context_create
  170. * @var array
  171. */
  172. var $_socketOptions = null;
  173. /**
  174. * Constructor
  175. *
  176. * Sets up the object
  177. * @param string The url to fetch/access
  178. * @param array Associative array of parameters which can have the following keys:
  179. * <ul>
  180. * <li>method - Method to use, GET, POST etc (string)</li>
  181. * <li>http - HTTP Version to use, 1.0 or 1.1 (string)</li>
  182. * <li>user - Basic Auth username (string)</li>
  183. * <li>pass - Basic Auth password (string)</li>
  184. * <li>proxy_host - Proxy server host (string)</li>
  185. * <li>proxy_port - Proxy server port (integer)</li>
  186. * <li>proxy_user - Proxy auth username (string)</li>
  187. * <li>proxy_pass - Proxy auth password (string)</li>
  188. * <li>timeout - Connection timeout in seconds (float)</li>
  189. * <li>allowRedirects - Whether to follow redirects or not (bool)</li>
  190. * <li>maxRedirects - Max number of redirects to follow (integer)</li>
  191. * <li>useBrackets - Whether to append [] to array variable names (bool)</li>
  192. * <li>saveBody - Whether to save response body in response object property (bool)</li>
  193. * <li>readTimeout - Timeout for reading / writing data over the socket (array (seconds, microseconds))</li>
  194. * <li>socketOptions - Options to pass to Net_Socket object (array)</li>
  195. * </ul>
  196. * @access public
  197. */
  198. function HTTP_Request($url = '', $params = array())
  199. {
  200. $this->_sock = &new Net_Socket();
  201. $this->_method = HTTP_REQUEST_METHOD_GET;
  202. $this->_http = HTTP_REQUEST_HTTP_VER_1_1;
  203. $this->_requestHeaders = array();
  204. $this->_postData = null;
  205. $this->_user = null;
  206. $this->_pass = null;
  207. $this->_proxy_host = null;
  208. $this->_proxy_port = null;
  209. $this->_proxy_user = null;
  210. $this->_proxy_pass = null;
  211. $this->_allowRedirects = false;
  212. $this->_maxRedirects = 3;
  213. $this->_redirects = 0;
  214. $this->_timeout = null;
  215. $this->_response = null;
  216. foreach ($params as $key => $value) {
  217. $this->{'_' . $key} = $value;
  218. }
  219. if (!empty($url)) {
  220. $this->setURL($url);
  221. }
  222. // Default useragent
  223. $this->addHeader('User-Agent', 'PEAR HTTP_Request class ( http://pear.php.net/ )');
  224. // Make sure keepalives dont knobble us
  225. $this->addHeader('Connection', 'close');
  226. // Basic authentication
  227. if (!empty($this->_user)) {
  228. $this->_requestHeaders['Authorization'] = 'Basic ' . base64_encode($this->_user . ':' . $this->_pass);
  229. }
  230. // Use gzip encoding if possible
  231. // Avoid gzip encoding if using multibyte functions (see #1781)
  232. if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && extension_loaded('zlib') &&
  233. 0 == (2 & ini_get('mbstring.func_overload'))) {
  234. $this->addHeader('Accept-Encoding', 'gzip');
  235. }
  236. }
  237. /**
  238. * Generates a Host header for HTTP/1.1 requests
  239. *
  240. * @access private
  241. * @return string
  242. */
  243. function _generateHostHeader()
  244. {
  245. if ($this->_url->port != 80 AND strcasecmp($this->_url->protocol, 'http') == 0) {
  246. $host = $this->_url->host . ':' . $this->_url->port;
  247. } elseif ($this->_url->port != 443 AND strcasecmp($this->_url->protocol, 'https') == 0) {
  248. $host = $this->_url->host . ':' . $this->_url->port;
  249. } elseif ($this->_url->port == 443 AND strcasecmp($this->_url->protocol, 'https') == 0 AND strpos($this->_url->url, ':443') !== false) {
  250. $host = $this->_url->host . ':' . $this->_url->port;
  251. } else {
  252. $host = $this->_url->host;
  253. }
  254. return $host;
  255. }
  256. /**
  257. * Resets the object to its initial state (DEPRECATED).
  258. * Takes the same parameters as the constructor.
  259. *
  260. * @param string $url The url to be requested
  261. * @param array $params Associative array of parameters
  262. * (see constructor for details)
  263. * @access public
  264. * @deprecated deprecated since 1.2, call the constructor if this is necessary
  265. */
  266. function reset($url, $params = array())
  267. {
  268. $this->HTTP_Request($url, $params);
  269. }
  270. /**
  271. * Sets the URL to be requested
  272. *
  273. * @param string The url to be requested
  274. * @access public
  275. */
  276. function setURL($url)
  277. {
  278. $this->_url = &new Net_URL($url, $this->_useBrackets);
  279. if (!empty($this->_url->user) || !empty($this->_url->pass)) {
  280. $this->setBasicAuth($this->_url->user, $this->_url->pass);
  281. }
  282. if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http) {
  283. $this->addHeader('Host', $this->_generateHostHeader());
  284. }
  285. }
  286. /**
  287. * Sets a proxy to be used
  288. *
  289. * @param string Proxy host
  290. * @param int Proxy port
  291. * @param string Proxy username
  292. * @param string Proxy password
  293. * @access public
  294. */
  295. function setProxy($host, $port = 8080, $user = null, $pass = null)
  296. {
  297. $this->_proxy_host = $host;
  298. $this->_proxy_port = $port;
  299. $this->_proxy_user = $user;
  300. $this->_proxy_pass = $pass;
  301. if (!empty($user)) {
  302. $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($user . ':' . $pass));
  303. }
  304. }
  305. /**
  306. * Sets basic authentication parameters
  307. *
  308. * @param string Username
  309. * @param string Password
  310. */
  311. function setBasicAuth($user, $pass)
  312. {
  313. $this->_user = $user;
  314. $this->_pass = $pass;
  315. $this->addHeader('Authorization', 'Basic ' . base64_encode($user . ':' . $pass));
  316. }
  317. /**
  318. * Sets the method to be used, GET, POST etc.
  319. *
  320. * @param string Method to use. Use the defined constants for this
  321. * @access public
  322. */
  323. function setMethod($method)
  324. {
  325. $this->_method = $method;
  326. }
  327. /**
  328. * Sets the HTTP version to use, 1.0 or 1.1
  329. *
  330. * @param string Version to use. Use the defined constants for this
  331. * @access public
  332. */
  333. function setHttpVer($http)
  334. {
  335. $this->_http = $http;
  336. }
  337. /**
  338. * Adds a request header
  339. *
  340. * @param string Header name
  341. * @param string Header value
  342. * @access public
  343. */
  344. function addHeader($name, $value)
  345. {
  346. $this->_requestHeaders[$name] = $value;
  347. }
  348. /**
  349. * Removes a request header
  350. *
  351. * @param string Header name to remove
  352. * @access public
  353. */
  354. function removeHeader($name)
  355. {
  356. if (isset($this->_requestHeaders[$name])) {
  357. unset($this->_requestHeaders[$name]);
  358. }
  359. }
  360. /**
  361. * Adds a querystring parameter
  362. *
  363. * @param string Querystring parameter name
  364. * @param string Querystring parameter value
  365. * @param bool Whether the value is already urlencoded or not, default = not
  366. * @access public
  367. */
  368. function addQueryString($name, $value, $preencoded = false)
  369. {
  370. $this->_url->addQueryString($name, $value, $preencoded);
  371. }
  372. /**
  373. * Sets the querystring to literally what you supply
  374. *
  375. * @param string The querystring data. Should be of the format foo=bar&x=y etc
  376. * @param bool Whether data is already urlencoded or not, default = already encoded
  377. * @access public
  378. */
  379. function addRawQueryString($querystring, $preencoded = true)
  380. {
  381. $this->_url->addRawQueryString($querystring, $preencoded);
  382. }
  383. /**
  384. * Adds postdata items
  385. *
  386. * @param string Post data name
  387. * @param string Post data value
  388. * @param bool Whether data is already urlencoded or not, default = not
  389. * @access public
  390. */
  391. function addPostData($name, $value, $preencoded = false)
  392. {
  393. if ($preencoded) {
  394. $this->_postData[$name] = $value;
  395. } else {
  396. $this->_postData[$name] = $this->_arrayMapRecursive('urlencode', $value);
  397. }
  398. }
  399. /**
  400. * Recursively applies the callback function to the value
  401. *
  402. * @param mixed Callback function
  403. * @param mixed Value to process
  404. * @access private
  405. * @return mixed Processed value
  406. */
  407. function _arrayMapRecursive($callback, $value)
  408. {
  409. if (!is_array($value)) {
  410. return call_user_func($callback, $value);
  411. } else {
  412. $map = array();
  413. foreach ($value as $k => $v) {
  414. $map[$k] = $this->_arrayMapRecursive($callback, $v);
  415. }
  416. return $map;
  417. }
  418. }
  419. /**
  420. * Adds a file to upload
  421. *
  422. * This also changes content-type to 'multipart/form-data' for proper upload
  423. *
  424. * @access public
  425. * @param string name of file-upload field
  426. * @param mixed file name(s)
  427. * @param mixed content-type(s) of file(s) being uploaded
  428. * @return bool true on success
  429. * @throws PEAR_Error
  430. */
  431. function addFile($inputName, $fileName, $contentType = 'application/octet-stream')
  432. {
  433. if (!is_array($fileName) && !is_readable($fileName)) {
  434. return PEAR::raiseError("File '{$fileName}' is not readable");
  435. } elseif (is_array($fileName)) {
  436. foreach ($fileName as $name) {
  437. if (!is_readable($name)) {
  438. return PEAR::raiseError("File '{$name}' is not readable");
  439. }
  440. }
  441. }
  442. $this->addHeader('Content-Type', 'multipart/form-data');
  443. $this->_postFiles[$inputName] = array(
  444. 'name' => $fileName,
  445. 'type' => $contentType
  446. );
  447. return true;
  448. }
  449. /**
  450. * Adds raw postdata
  451. *
  452. * @param string The data
  453. * @param bool Whether data is preencoded or not, default = already encoded
  454. * @access public
  455. */
  456. function addRawPostData($postdata, $preencoded = true)
  457. {
  458. $this->_postData = $preencoded ? $postdata : urlencode($postdata);
  459. }
  460. /**
  461. * Clears any postdata that has been added (DEPRECATED).
  462. *
  463. * Useful for multiple request scenarios.
  464. *
  465. * @access public
  466. * @deprecated deprecated since 1.2
  467. */
  468. function clearPostData()
  469. {
  470. $this->_postData = null;
  471. }
  472. /**
  473. * Appends a cookie to "Cookie:" header
  474. *
  475. * @param string $name cookie name
  476. * @param string $value cookie value
  477. * @access public
  478. */
  479. function addCookie($name, $value)
  480. {
  481. $cookies = isset($this->_requestHeaders['Cookie']) ? $this->_requestHeaders['Cookie']. '; ' : '';
  482. $this->addHeader('Cookie', $cookies . $name . '=' . $value);
  483. }
  484. /**
  485. * Clears any cookies that have been added (DEPRECATED).
  486. *
  487. * Useful for multiple request scenarios
  488. *
  489. * @access public
  490. * @deprecated deprecated since 1.2
  491. */
  492. function clearCookies()
  493. {
  494. $this->removeHeader('Cookie');
  495. }
  496. /**
  497. * Sends the request
  498. *
  499. * @access public
  500. * @param bool Whether to store response body in Response object property,
  501. * set this to false if downloading a LARGE file and using a Listener
  502. * @return mixed PEAR error on error, true otherwise
  503. */
  504. function sendRequest($saveBody = true)
  505. {
  506. if (!is_a($this->_url, 'Net_URL')) {
  507. return PEAR::raiseError('No URL given.');
  508. }
  509. $host = isset($this->_proxy_host) ? $this->_proxy_host : $this->_url->host;
  510. $port = isset($this->_proxy_port) ? $this->_proxy_port : $this->_url->port;
  511. // 4.3.0 supports SSL connections using OpenSSL. The function test determines
  512. // we running on at least 4.3.0
  513. if (strcasecmp($this->_url->protocol, 'https') == 0 AND function_exists('file_get_contents') AND extension_loaded('openssl')) {
  514. if (isset($this->_proxy_host)) {
  515. return PEAR::raiseError('HTTPS proxies are not supported.');
  516. }
  517. $host = 'ssl://' . $host;
  518. }
  519. // If this is a second request, we may get away without
  520. // re-connecting if they're on the same server
  521. if (PEAR::isError($err = $this->_sock->connect($host, $port, null, $this->_timeout, $this->_socketOptions)) ||
  522. PEAR::isError($err = $this->_sock->write($this->_buildRequest()))) {
  523. return $err;
  524. }
  525. if (!empty($this->_readTimeout)) {
  526. $this->_sock->setTimeout($this->_readTimeout[0], $this->_readTimeout[1]);
  527. }
  528. $this->_notify('sentRequest');
  529. // Read the response
  530. $this->_response = &new HTTP_Response($this->_sock, $this->_listeners);
  531. if (PEAR::isError($err = $this->_response->process($this->_saveBody && $saveBody)) ) {
  532. return $err;
  533. }
  534. // Check for redirection
  535. // Bugfix (PEAR) bug #18, 6 oct 2003 by Dave Mertens (headers are also stored lowercase, so we're gonna use them here)
  536. // some non RFC2616 compliant servers (scripts) are returning lowercase headers ('location: xxx')
  537. if ( $this->_allowRedirects
  538. AND $this->_redirects <= $this->_maxRedirects
  539. AND $this->getResponseCode() > 300
  540. AND $this->getResponseCode() < 399
  541. AND !empty($this->_response->_headers['location'])) {
  542. $redirect = $this->_response->_headers['location'];
  543. // Absolute URL
  544. if (preg_match('/^https?:\/\//i', $redirect)) {
  545. $this->_url = &new Net_URL($redirect);
  546. $this->addHeader('Host', $this->_generateHostHeader());
  547. // Absolute path
  548. } elseif ($redirect{0} == '/') {
  549. $this->_url->path = $redirect;
  550. // Relative path
  551. } elseif (substr($redirect, 0, 3) == '../' OR substr($redirect, 0, 2) == './') {
  552. if (substr($this->_url->path, -1) == '/') {
  553. $redirect = $this->_url->path . $redirect;
  554. } else {
  555. $redirect = dirname($this->_url->path) . '/' . $redirect;
  556. }
  557. $redirect = Net_URL::resolvePath($redirect);
  558. $this->_url->path = $redirect;
  559. // Filename, no path
  560. } else {
  561. if (substr($this->_url->path, -1) == '/') {
  562. $redirect = $this->_url->path . $redirect;
  563. } else {
  564. $redirect = dirname($this->_url->path) . '/' . $redirect;
  565. }
  566. $this->_url->path = $redirect;
  567. }
  568. $this->_redirects++;
  569. return $this->sendRequest($saveBody);
  570. // Too many redirects
  571. } elseif ($this->_allowRedirects AND $this->_redirects > $this->_maxRedirects) {
  572. return PEAR::raiseError('Too many redirects');
  573. }
  574. $this->_sock->disconnect();
  575. return true;
  576. }
  577. /**
  578. * Returns the response code
  579. *
  580. * @access public
  581. * @return mixed Response code, false if not set
  582. */
  583. function getResponseCode()
  584. {
  585. return isset($this->_response->_code) ? $this->_response->_code : false;
  586. }
  587. /**
  588. * Returns either the named header or all if no name given
  589. *
  590. * @access public
  591. * @param string The header name to return, do not set to get all headers
  592. * @return mixed either the value of $headername (false if header is not present)
  593. * or an array of all headers
  594. */
  595. function getResponseHeader($headername = null)
  596. {
  597. if (!isset($headername)) {
  598. return isset($this->_response->_headers)? $this->_response->_headers: array();
  599. } else {
  600. return isset($this->_response->_headers[$headername]) ? $this->_response->_headers[$headername] : false;
  601. }
  602. }
  603. /**
  604. * Returns the body of the response
  605. *
  606. * @access public
  607. * @return mixed response body, false if not set
  608. */
  609. function getResponseBody()
  610. {
  611. return isset($this->_response->_body) ? $this->_response->_body : false;
  612. }
  613. /**
  614. * Returns cookies set in response
  615. *
  616. * @access public
  617. * @return mixed array of response cookies, false if none are present
  618. */
  619. function getResponseCookies()
  620. {
  621. return isset($this->_response->_cookies) ? $this->_response->_cookies : false;
  622. }
  623. /**
  624. * Builds the request string
  625. *
  626. * @access private
  627. * @return string The request string
  628. */
  629. function _buildRequest()
  630. {
  631. $separator = ini_get('arg_separator.output');
  632. ini_set('arg_separator.output', '&');
  633. $querystring = ($querystring = $this->_url->getQueryString()) ? '?' . $querystring : '';
  634. ini_set('arg_separator.output', $separator);
  635. $host = isset($this->_proxy_host) ? $this->_url->protocol . '://' . $this->_url->host : '';
  636. $port = (isset($this->_proxy_host) AND $this->_url->port != 80) ? ':' . $this->_url->port : '';
  637. $path = (empty($this->_url->path)? '/': $this->_url->path) . $querystring;
  638. $url = $host . $port . $path;
  639. $request = $this->_method . ' ' . $url . ' HTTP/' . $this->_http . "\r\n";
  640. if (HTTP_REQUEST_METHOD_POST != $this->_method && HTTP_REQUEST_METHOD_PUT != $this->_method) {
  641. $this->removeHeader('Content-Type');
  642. } else {
  643. if (empty($this->_requestHeaders['Content-Type'])) {
  644. // Add default content-type
  645. $this->addHeader('Content-Type', 'application/x-www-form-urlencoded');
  646. } elseif ('multipart/form-data' == $this->_requestHeaders['Content-Type']) {
  647. $boundary = 'HTTP_Request_' . md5(uniqid('request') . microtime());
  648. $this->addHeader('Content-Type', 'multipart/form-data; boundary=' . $boundary);
  649. }
  650. }
  651. // Request Headers
  652. if (!empty($this->_requestHeaders)) {
  653. foreach ($this->_requestHeaders as $name => $value) {
  654. $request .= $name . ': ' . $value . "\r\n";
  655. }
  656. }
  657. // No post data or wrong method, so simply add a final CRLF
  658. if ((HTTP_REQUEST_METHOD_POST != $this->_method && HTTP_REQUEST_METHOD_PUT != $this->_method) ||
  659. (empty($this->_postData) && empty($this->_postFiles))) {
  660. $request .= "\r\n";
  661. // Post data if it's an array
  662. } elseif ((!empty($this->_postData) && is_array($this->_postData)) || !empty($this->_postFiles)) {
  663. // "normal" POST request
  664. if (!isset($boundary)) {
  665. $postdata = implode('&', array_map(
  666. create_function('$a', 'return $a[0] . \'=\' . $a[1];'),
  667. $this->_flattenArray('', $this->_postData)
  668. ));
  669. // multipart request, probably with file uploads
  670. } else {
  671. $postdata = '';
  672. if (!empty($this->_postData)) {
  673. $flatData = $this->_flattenArray('', $this->_postData);
  674. foreach ($flatData as $item) {
  675. $postdata .= '--' . $boundary . "\r\n";
  676. $postdata .= 'Content-Disposition: form-data; name="' . $item[0] . '"';
  677. $postdata .= "\r\n\r\n" . urldecode($item[1]) . "\r\n";
  678. }
  679. }
  680. foreach ($this->_postFiles as $name => $value) {
  681. if (is_array($value['name'])) {
  682. $varname = $name . ($this->_useBrackets? '[]': '');
  683. } else {
  684. $varname = $name;
  685. $value['name'] = array($value['name']);
  686. }
  687. foreach ($value['name'] as $key => $filename) {
  688. $fp = fopen($filename, 'r');
  689. $data = fread($fp, filesize($filename));
  690. fclose($fp);
  691. $basename = basename($filename);
  692. $type = is_array($value['type'])? @$value['type'][$key]: $value['type'];
  693. $postdata .= '--' . $boundary . "\r\n";
  694. $postdata .= 'Content-Disposition: form-data; name="' . $varname . '"; filename="' . $basename . '"';
  695. $postdata .= "\r\nContent-Type: " . $type;
  696. $postdata .= "\r\n\r\n" . $data . "\r\n";
  697. }
  698. }
  699. $postdata .= '--' . $boundary . "\r\n";
  700. }
  701. $request .= 'Content-Length: ' . strlen($postdata) . "\r\n\r\n";
  702. $request .= $postdata;
  703. // Post data if it's raw
  704. } elseif(!empty($this->_postData)) {
  705. $request .= 'Content-Length: ' . strlen($this->_postData) . "\r\n\r\n";
  706. $request .= $this->_postData;
  707. }
  708. return $request;
  709. }
  710. /**
  711. * Helper function to change the (probably multidimensional) associative array
  712. * into the simple one.
  713. *
  714. * @param string name for item
  715. * @param mixed item's values
  716. * @return array array with the following items: array('item name', 'item value');
  717. */
  718. function _flattenArray($name, $values)
  719. {
  720. if (!is_array($values)) {
  721. return array(array($name, $values));
  722. } else {
  723. $ret = array();
  724. foreach ($values as $k => $v) {
  725. if (empty($name)) {
  726. $newName = $k;
  727. } elseif ($this->_useBrackets) {
  728. $newName = $name . '[' . $k . ']';
  729. } else {
  730. $newName = $name;
  731. }
  732. $ret = array_merge($ret, $this->_flattenArray($newName, $v));
  733. }
  734. return $ret;
  735. }
  736. }
  737. /**
  738. * Adds a Listener to the list of listeners that are notified of
  739. * the object's events
  740. *
  741. * @param object HTTP_Request_Listener instance to attach
  742. * @return boolean whether the listener was successfully attached
  743. * @access public
  744. */
  745. function attach(&$listener)
  746. {
  747. if (!is_a($listener, 'HTTP_Request_Listener')) {
  748. return false;
  749. }
  750. $this->_listeners[$listener->getId()] =& $listener;
  751. return true;
  752. }
  753. /**
  754. * Removes a Listener from the list of listeners
  755. *
  756. * @param object HTTP_Request_Listener instance to detach
  757. * @return boolean whether the listener was successfully detached
  758. * @access public
  759. */
  760. function detach(&$listener)
  761. {
  762. if (!is_a($listener, 'HTTP_Request_Listener') ||
  763. !isset($this->_listeners[$listener->getId()])) {
  764. return false;
  765. }
  766. unset($this->_listeners[$listener->getId()]);
  767. return true;
  768. }
  769. /**
  770. * Notifies all registered listeners of an event.
  771. *
  772. * Events sent by HTTP_Request object
  773. * 'sentRequest': after the request was sent
  774. * Events sent by HTTP_Response object
  775. * 'gotHeaders': after receiving response headers (headers are passed in $data)
  776. * 'tick': on receiving a part of response body (the part is passed in $data)
  777. * 'gzTick': on receiving a gzip-encoded part of response body (ditto)
  778. * 'gotBody': after receiving the response body (passes the decoded body in $data if it was gzipped)
  779. *
  780. * @param string Event name
  781. * @param mixed Additional data
  782. * @access private
  783. */
  784. function _notify($event, $data = null)
  785. {
  786. foreach (array_keys($this->_listeners) as $id) {
  787. $this->_listeners[$id]->update($this, $event, $data);
  788. }
  789. }
  790. }
  791. /**
  792. * Response class to complement the Request class
  793. */
  794. class HTTP_Response
  795. {
  796. /**
  797. * Socket object
  798. * @var object
  799. */
  800. var $_sock;
  801. /**
  802. * Protocol
  803. * @var string
  804. */
  805. var $_protocol;
  806. /**
  807. * Return code
  808. * @var string
  809. */
  810. var $_code;
  811. /**
  812. * Response headers
  813. * @var array
  814. */
  815. var $_headers;
  816. /**
  817. * Cookies set in response
  818. * @var array
  819. */
  820. var $_cookies;
  821. /**
  822. * Response body
  823. * @var string
  824. */
  825. var $_body = '';
  826. /**
  827. * Used by _readChunked(): remaining length of the current chunk
  828. * @var string
  829. */
  830. var $_chunkLength = 0;
  831. /**
  832. * Attached listeners
  833. * @var array
  834. */
  835. var $_listeners = array();
  836. /**
  837. * Constructor
  838. *
  839. * @param object Net_Socket socket to read the response from
  840. * @param array listeners attached to request
  841. * @return mixed PEAR Error on error, true otherwise
  842. */
  843. function HTTP_Response(&$sock, &$listeners)
  844. {
  845. $this->_sock =& $sock;
  846. $this->_listeners =& $listeners;
  847. }
  848. /**
  849. * Processes a HTTP response
  850. *
  851. * This extracts response code, headers, cookies and decodes body if it
  852. * was encoded in some way
  853. *
  854. * @access public
  855. * @param bool Whether to store response body in object property, set
  856. * this to false if downloading a LARGE file and using a Listener.
  857. * This is assumed to be true if body is gzip-encoded.
  858. * @throws PEAR_Error
  859. * @return mixed true on success, PEAR_Error in case of malformed response
  860. */
  861. function process($saveBody = true)
  862. {
  863. do {
  864. $line = $this->_sock->readLine();
  865. if (sscanf($line, 'HTTP/%s %s', $http_version, $returncode) != 2) {
  866. return PEAR::raiseError('Malformed response.');
  867. } else {
  868. $this->_protocol = 'HTTP/' . $http_version;
  869. $this->_code = intval($returncode);
  870. }
  871. while ('' !== ($header = $this->_sock->readLine())) {
  872. $this->_processHeader($header);
  873. }
  874. } while (100 == $this->_code);
  875. $this->_notify('gotHeaders', $this->_headers);
  876. // If response body is present, read it and decode
  877. $chunked = isset($this->_headers['transfer-encoding']) && ('chunked' == $this->_headers['transfer-encoding']);
  878. $gzipped = isset($this->_headers['content-encoding']) && ('gzip' == $this->_headers['content-encoding']);
  879. $hasBody = false;
  880. while (!$this->_sock->eof()) {
  881. if ($chunked) {
  882. $data = $this->_readChunked();
  883. } else {
  884. $data = $this->_sock->read(4096);
  885. }
  886. if ('' != $data) {
  887. $hasBody = true;
  888. if ($saveBody || $gzipped) {
  889. $this->_body .= $data;
  890. }
  891. $this->_notify($gzipped? 'gzTick': 'tick', $data);
  892. }
  893. }
  894. if ($hasBody) {
  895. // Uncompress the body if needed
  896. if ($gzipped) {
  897. $this->_body = gzinflate(substr($this->_body, 10));
  898. $this->_notify('gotBody', $this->_body);
  899. } else {
  900. $this->_notify('gotBody');
  901. }
  902. }
  903. return true;
  904. }
  905. /**
  906. * Processes the response header
  907. *
  908. * @access private
  909. * @param string HTTP header
  910. */
  911. function _processHeader($header)
  912. {
  913. list($headername, $headervalue) = explode(':', $header, 2);
  914. $headername_i = strtolower($headername);
  915. $headervalue = ltrim($headervalue);
  916. if ('set-cookie' != $headername_i) {
  917. $this->_headers[$headername] = $headervalue;
  918. $this->_headers[$headername_i] = $headervalue;
  919. } else {
  920. $this->_parseCookie($headervalue);
  921. }
  922. }
  923. /**
  924. * Parse a Set-Cookie header to fill $_cookies array
  925. *
  926. * @access private
  927. * @param string value of Set-Cookie header
  928. */
  929. function _parseCookie($headervalue)
  930. {
  931. $cookie = array(
  932. 'expires' => null,
  933. 'domain' => null,
  934. 'path' => null,
  935. 'secure' => false
  936. );
  937. // Only a name=value pair
  938. if (!strpos($headervalue, ';')) {
  939. $pos = strpos($headervalue, '=');
  940. $cookie['name'] = trim(substr($headervalue, 0, $pos));
  941. $cookie['value'] = trim(substr($headervalue, $pos + 1));
  942. // Some optional parameters are supplied
  943. } else {
  944. $elements = explode(';', $headervalue);
  945. $pos = strpos($elements[0], '=');
  946. $cookie['name'] = trim(substr($elements[0], 0, $pos));
  947. $cookie['value'] = trim(substr($elements[0], $pos + 1));
  948. for ($i = 1; $i < count($elements); $i++) {
  949. if (false === strpos($elements[$i], '=')) {
  950. $elName = trim($elements[$i]);
  951. $elValue = null;
  952. } else {
  953. list ($elName, $elValue) = array_map('trim', explode('=', $elements[$i]));
  954. }
  955. $elName = strtolower($elName);
  956. if ('secure' == $elName) {
  957. $cookie['secure'] = true;
  958. } elseif ('expires' == $elName) {
  959. $cookie['expires'] = str_replace('"', '', $elValue);
  960. } elseif ('path' == $elName || 'domain' == $elName) {
  961. $cookie[$elName] = urldecode($elValue);
  962. } else {
  963. $cookie[$elName] = $elValue;
  964. }
  965. }
  966. }
  967. $this->_cookies[] = $cookie;
  968. }
  969. /**
  970. * Read a part of response body encoded with chunked Transfer-Encoding
  971. *
  972. * @access private
  973. * @return string
  974. */
  975. function _readChunked()
  976. {
  977. // at start of the next chunk?
  978. if (0 == $this->_chunkLength) {
  979. $line = $this->_sock->readLine();
  980. if (preg_match('/^([0-9a-f]+)/i', $line, $matches)) {
  981. $this->_chunkLength = hexdec($matches[1]);
  982. // Chunk with zero length indicates the end
  983. if (0 == $this->_chunkLength) {
  984. $this->_sock->readAll(); // make this an eof()
  985. return '';
  986. }
  987. } elseif ($this->_sock->eof()) {
  988. return '';
  989. }
  990. }
  991. $data = $this->_sock->read($this->_chunkLength);
  992. $this->_chunkLength -= strlen($data);
  993. if (0 == $this->_chunkLength) {
  994. $this->_sock->readLine(); // Trailing CRLF
  995. }
  996. return $data;
  997. }
  998. /**
  999. * Notifies all registered listeners of an event.
  1000. *
  1001. * @param string Event name
  1002. * @param mixed Additional data
  1003. * @access private
  1004. * @see HTTP_Request::_notify()
  1005. */
  1006. function _notify($event, $data = null)
  1007. {
  1008. foreach (array_keys($this->_listeners) as $id) {
  1009. $this->_listeners[$id]->update($this, $event, $data);
  1010. }
  1011. }
  1012. } // End class HTTP_Response
  1013. ?>