Request.php 34 KB

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