PageRenderTime 45ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/library/Zend/Http/Client.php

https://bitbucket.org/baruffaldi/website-insaneminds
PHP | 1086 lines | 504 code | 147 blank | 435 comment | 127 complexity | 952f325014047273026db19f549264f1 MD5 | raw file
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Http
  17. * @subpackage Client
  18. * @version $Id: Client.php 8064 2008-02-16 10:58:39Z thomas $
  19. * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  20. * @license http://framework.zend.com/license/new-bsd New BSD License
  21. */
  22. require_once 'Zend/Loader.php';
  23. require_once 'Zend/Uri.php';
  24. require_once 'Zend/Http/Client/Adapter/Interface.php';
  25. require_once 'Zend/Http/Response.php';
  26. /**
  27. * Zend_Http_Client is an implemetation of an HTTP client in PHP. The client
  28. * supports basic features like sending different HTTP requests and handling
  29. * redirections, as well as more advanced features like proxy settings, HTTP
  30. * authentication and cookie persistance (using a Zend_Http_CookieJar object)
  31. *
  32. * @todo Implement proxy settings
  33. * @category Zend
  34. * @package Zend_Http
  35. * @subpackage Client
  36. * @throws Zend_Http_Client_Exception
  37. * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  38. * @license http://framework.zend.com/license/new-bsd New BSD License
  39. */
  40. class Zend_Http_Client
  41. {
  42. /**
  43. * HTTP request methods
  44. */
  45. const GET = 'GET';
  46. const POST = 'POST';
  47. const PUT = 'PUT';
  48. const HEAD = 'HEAD';
  49. const DELETE = 'DELETE';
  50. const TRACE = 'TRACE';
  51. const OPTIONS = 'OPTIONS';
  52. const CONNECT = 'CONNECT';
  53. /**
  54. * Supported HTTP Authentication methods
  55. */
  56. const AUTH_BASIC = 'basic';
  57. //const AUTH_DIGEST = 'digest'; <-- not implemented yet
  58. /**
  59. * HTTP protocol versions
  60. */
  61. const HTTP_1 = '1.1';
  62. const HTTP_0 = '1.0';
  63. /**
  64. * POST data encoding methods
  65. */
  66. const ENC_URLENCODED = 'application/x-www-form-urlencoded';
  67. const ENC_FORMDATA = 'multipart/form-data';
  68. /**
  69. * Configuration array, set using the constructor or using ::setConfig()
  70. *
  71. * @var unknown_type
  72. */
  73. protected $config = array(
  74. 'maxredirects' => 5,
  75. 'strictredirects' => false,
  76. 'useragent' => 'Zend_Http_Client',
  77. 'timeout' => 10,
  78. 'adapter' => 'Zend_Http_Client_Adapter_Socket',
  79. 'httpversion' => self::HTTP_1,
  80. 'keepalive' => false,
  81. 'storeresponse' => true,
  82. 'strict' => true
  83. );
  84. /**
  85. * The adapter used to preform the actual connection to the server
  86. *
  87. * @var Zend_Http_Client_Adapter_Interface
  88. */
  89. protected $adapter = null;
  90. /**
  91. * Request URI
  92. *
  93. * @var Zend_Uri_Http
  94. */
  95. protected $uri;
  96. /**
  97. * Associative array of request headers
  98. *
  99. * @var array
  100. */
  101. protected $headers = array();
  102. /**
  103. * HTTP request method
  104. *
  105. * @var string
  106. */
  107. protected $method = self::GET;
  108. /**
  109. * Associative array of GET parameters
  110. *
  111. * @var array
  112. */
  113. protected $paramsGet = array();
  114. /**
  115. * Assiciative array of POST parameters
  116. *
  117. * @var array
  118. */
  119. protected $paramsPost = array();
  120. /**
  121. * Request body content type (for POST requests)
  122. *
  123. * @var string
  124. */
  125. protected $enctype = null;
  126. /**
  127. * The raw post data to send. Could be set by setRawData($data, $enctype).
  128. *
  129. * @var string
  130. */
  131. protected $raw_post_data = null;
  132. /**
  133. * HTTP Authentication settings
  134. *
  135. * Expected to be an associative array with this structure:
  136. * $this->auth = array('user' => 'username', 'password' => 'password', 'type' => 'basic')
  137. * Where 'type' should be one of the supported authentication types (see the AUTH_*
  138. * constants), for example 'basic' or 'digest'.
  139. *
  140. * If null, no authentication will be used.
  141. *
  142. * @var array|null
  143. */
  144. protected $auth;
  145. /**
  146. * File upload arrays (used in POST requests)
  147. *
  148. * An associative array, where each element is of the format:
  149. * 'name' => array('filename.txt', 'text/plain', 'This is the actual file contents')
  150. *
  151. * @var array
  152. */
  153. protected $files = array();
  154. /**
  155. * The client's cookie jar
  156. *
  157. * @var Zend_Http_CookieJar
  158. */
  159. protected $cookiejar = null;
  160. /**
  161. * The last HTTP request sent by the client, as string
  162. *
  163. * @var string
  164. */
  165. protected $last_request = null;
  166. /**
  167. * The last HTTP response received by the client
  168. *
  169. * @var Zend_Http_Response
  170. */
  171. protected $last_response = null;
  172. /**
  173. * Redirection counter
  174. *
  175. * @var int
  176. */
  177. protected $redirectCounter = 0;
  178. /**
  179. * Contructor method. Will create a new HTTP client. Accepts the target
  180. * URL and optionally and array of headers.
  181. *
  182. * @param Zend_Uri_Http|string $uri
  183. * @param array $headers Optional request headers to set
  184. */
  185. public function __construct($uri = null, $config = null)
  186. {
  187. if ($uri !== null) $this->setUri($uri);
  188. if ($config !== null) $this->setConfig($config);
  189. }
  190. /**
  191. * Set the URI for the next request
  192. *
  193. * @param Zend_Uri_Http|string $uri
  194. * @return Zend_Http_Client
  195. * @throws Zend_Http_Client_Exception
  196. */
  197. public function setUri($uri)
  198. {
  199. if (is_string($uri)) {
  200. $uri = Zend_Uri::factory($uri);
  201. }
  202. if (!$uri instanceof Zend_Uri_Http) {
  203. require_once 'Zend/Http/Client/Exception.php';
  204. throw new Zend_Http_Client_Exception('Passed parameter is not a valid HTTP URI.');
  205. }
  206. // We have no ports, set the defaults
  207. if (! $uri->getPort()) {
  208. $uri->setPort(($uri->getScheme() == 'https' ? 443 : 80));
  209. }
  210. $this->uri = $uri;
  211. return $this;
  212. }
  213. /**
  214. * Get the URI for the next request
  215. *
  216. * @param boolean $as_string If true, will return the URI as a string
  217. * @return Zend_Uri_Http|string
  218. */
  219. public function getUri($as_string = false)
  220. {
  221. if ($as_string && $this->uri instanceof Zend_Uri_Http) {
  222. return $this->uri->__toString();
  223. } else {
  224. return $this->uri;
  225. }
  226. }
  227. /**
  228. * Set configuration parameters for this HTTP client
  229. *
  230. * @param array $config
  231. * @return Zend_Http_Client
  232. */
  233. public function setConfig($config = array())
  234. {
  235. if (! is_array($config)) {
  236. require_once 'Zend/Http/Client/Exception.php';
  237. throw new Zend_Http_Client_Exception('Expected array parameter, given ' . gettype($config));
  238. }
  239. foreach ($config as $k => $v)
  240. $this->config[strtolower($k)] = $v;
  241. return $this;
  242. }
  243. /**
  244. * Set the next request's method
  245. *
  246. * Validated the passed method and sets it. If we have files set for
  247. * POST requests, and the new method is not POST, the files are silently
  248. * dropped.
  249. *
  250. * @param string $method
  251. * @return Zend_Http_Client
  252. */
  253. public function setMethod($method = self::GET)
  254. {
  255. if (! preg_match('/^[A-Za-z_]+$/', $method)) {
  256. require_once 'Zend/Http/Client/Exception.php';
  257. throw new Zend_Http_Client_Exception("'{$method}' is not a valid HTTP request method.");
  258. }
  259. if ($method == self::POST && $this->enctype === null)
  260. $this->setEncType(self::ENC_URLENCODED);
  261. $this->method = $method;
  262. return $this;
  263. }
  264. /**
  265. * Set one or more request headers
  266. *
  267. * This function can be used in several ways to set the client's request
  268. * headers:
  269. * 1. By providing two parameters: $name as the header to set (eg. 'Host')
  270. * and $value as it's value (eg. 'www.example.com').
  271. * 2. By providing a single header string as the only parameter
  272. * eg. 'Host: www.example.com'
  273. * 3. By providing an array of headers as the first parameter
  274. * eg. array('host' => 'www.example.com', 'x-foo: bar'). In This case
  275. * the function will call itself recursively for each array item.
  276. *
  277. * @param string|array $name Header name, full header string ('Header: value')
  278. * or an array of headers
  279. * @param mixed $value Header value or null
  280. * @return Zend_Http_Client
  281. */
  282. public function setHeaders($name, $value = null)
  283. {
  284. // If we got an array, go recusive!
  285. if (is_array($name)) {
  286. foreach ($name as $k => $v) {
  287. if (is_string($k)) {
  288. $this->setHeaders($k, $v);
  289. } else {
  290. $this->setHeaders($v, null);
  291. }
  292. }
  293. } else {
  294. // Check if $name needs to be split
  295. if ($value === null && (strpos($name, ':') > 0))
  296. list($name, $value) = explode(':', $name, 2);
  297. // Make sure the name is valid if we are in strict mode
  298. if ($this->config['strict'] && (! preg_match('/^[a-zA-Z0-9-]+$/', $name))) {
  299. require_once 'Zend/Http/Client/Exception.php';
  300. throw new Zend_Http_Client_Exception("{$name} is not a valid HTTP header name");
  301. }
  302. $normalized_name = strtolower($name);
  303. // If $value is null or false, unset the header
  304. if ($value === null || $value === false) {
  305. unset($this->headers[$normalized_name]);
  306. // Else, set the header
  307. } else {
  308. // Header names are storred lowercase internally.
  309. if (is_string($value)) $value = trim($value);
  310. $this->headers[$normalized_name] = array($name, $value);
  311. }
  312. }
  313. return $this;
  314. }
  315. /**
  316. * Get the value of a specific header
  317. *
  318. * Note that if the header has more than one value, an array
  319. * will be returned.
  320. *
  321. * @param unknown_type $key
  322. * @return string|array|null The header value or null if it is not set
  323. */
  324. public function getHeader($key)
  325. {
  326. $key = strtolower($key);
  327. if (isset($this->headers[$key])) {
  328. return $this->headers[$key][1];
  329. } else {
  330. return null;
  331. }
  332. }
  333. /**
  334. * Set a GET parameter for the request. Wrapper around _setParameter
  335. *
  336. * @param string|array $name
  337. * @param string $value
  338. * @return Zend_Http_Client
  339. */
  340. public function setParameterGet($name, $value = null)
  341. {
  342. if (is_array($name)) {
  343. foreach ($name as $k => $v)
  344. $this->_setParameter('GET', $k, $v);
  345. } else {
  346. $this->_setParameter('GET', $name, $value);
  347. }
  348. return $this;
  349. }
  350. /**
  351. * Set a POST parameter for the request. Wrapper around _setParameter
  352. *
  353. * @param string|array $name
  354. * @param string $value
  355. * @return Zend_Http_Client
  356. */
  357. public function setParameterPost($name, $value = null)
  358. {
  359. if (is_array($name)) {
  360. foreach ($name as $k => $v)
  361. $this->_setParameter('POST', $k, $v);
  362. } else {
  363. $this->_setParameter('POST', $name, $value);
  364. }
  365. return $this;
  366. }
  367. /**
  368. * Set a GET or POST parameter - used by SetParameterGet and SetParameterPost
  369. *
  370. * @param string $type GET or POST
  371. * @param string $name
  372. * @param string $value
  373. */
  374. protected function _setParameter($type, $name, $value)
  375. {
  376. $parray = array();
  377. $type = strtolower($type);
  378. switch ($type) {
  379. case 'get':
  380. $parray = &$this->paramsGet;
  381. break;
  382. case 'post':
  383. $parray = &$this->paramsPost;
  384. break;
  385. }
  386. if ($value === null) {
  387. if (isset($parray[$name])) unset($parray[$name]);
  388. } else {
  389. $parray[$name] = $value;
  390. }
  391. }
  392. /**
  393. * Get the number of redirections done on the last request
  394. *
  395. * @return int
  396. */
  397. public function getRedirectionsCount()
  398. {
  399. return $this->redirectCounter;
  400. }
  401. /**
  402. * Set HTTP authentication parameters
  403. *
  404. * $type should be one of the supported types - see the self::AUTH_*
  405. * constants.
  406. *
  407. * To enable authentication:
  408. * <code>
  409. * $this->setAuth('shahar', 'secret', Zend_Http_Client::AUTH_BASIC);
  410. * </code>
  411. *
  412. * To disable authentication:
  413. * <code>
  414. * $this->setAuth(false);
  415. * </code>
  416. *
  417. * @see http://www.faqs.org/rfcs/rfc2617.html
  418. * @param string|false $user User name or false disable authentication
  419. * @param string $password Password
  420. * @param string $type Authentication type
  421. * @return Zend_Http_Client
  422. */
  423. public function setAuth($user, $password = '', $type = self::AUTH_BASIC)
  424. {
  425. // If we got false or null, disable authentication
  426. if ($user === false || $user === null) {
  427. $this->auth = null;
  428. // Else, set up authentication
  429. } else {
  430. // Check we got a proper authentication type
  431. if (! defined('self::AUTH_' . strtoupper($type))) {
  432. require_once 'Zend/Http/Client/Exception.php';
  433. throw new Zend_Http_Client_Exception("Invalid or not supported authentication type: '$type'");
  434. }
  435. $this->auth = array(
  436. 'user' => (string) $user,
  437. 'password' => (string) $password,
  438. 'type' => $type
  439. );
  440. }
  441. return $this;
  442. }
  443. /**
  444. * Set the HTTP client's cookie jar.
  445. *
  446. * A cookie jar is an object that holds and maintains cookies across HTTP requests
  447. * and responses.
  448. *
  449. * @param Zend_Http_CookieJar|boolean $cookiejar Existing cookiejar object, true to create a new one, false to disable
  450. * @return Zend_Http_Client
  451. */
  452. public function setCookieJar($cookiejar = true)
  453. {
  454. if (! class_exists('Zend_Http_CookieJar'))
  455. require_once 'Zend/Http/CookieJar.php';
  456. if ($cookiejar instanceof Zend_Http_CookieJar) {
  457. $this->cookiejar = $cookiejar;
  458. } elseif ($cookiejar === true) {
  459. $this->cookiejar = new Zend_Http_CookieJar();
  460. } elseif (! $cookiejar) {
  461. $this->cookiejar = null;
  462. } else {
  463. require_once 'Zend/Http/Client/Exception.php';
  464. throw new Zend_Http_Client_Exception('Invalid parameter type passed as CookieJar');
  465. }
  466. return $this;
  467. }
  468. /**
  469. * Return the current cookie jar or null if none.
  470. *
  471. * @return Zend_Http_CookieJar|null
  472. */
  473. public function getCookieJar()
  474. {
  475. return $this->cookiejar;
  476. }
  477. /**
  478. * Add a cookie to the request. If the client has no Cookie Jar, the cookies
  479. * will be added directly to the headers array as "Cookie" headers.
  480. *
  481. * @param Zend_Http_Cookie|string $cookie
  482. * @param string|null $value If "cookie" is a string, this is the cookie value.
  483. * @return Zend_Http_Client
  484. */
  485. public function setCookie($cookie, $value = null)
  486. {
  487. if (! class_exists('Zend_Http_Cookie'))
  488. require_once 'Zend/Http/Cookie.php';
  489. if (is_array($cookie)) {
  490. foreach ($cookie as $c => $v) {
  491. if (is_string($c)) {
  492. $this->setCookie($c, $v);
  493. } else {
  494. $this->setCookie($v);
  495. }
  496. }
  497. return $this;
  498. }
  499. if ($value !== null) $value = urlencode($value);
  500. if (isset($this->cookiejar)) {
  501. if ($cookie instanceof Zend_Http_Cookie) {
  502. $this->cookiejar->addCookie($cookie);
  503. } elseif (is_string($cookie) && $value !== null) {
  504. $cookie = Zend_Http_Cookie::fromString("{$cookie}={$value}", $this->uri);
  505. $this->cookiejar->addCookie($cookie);
  506. }
  507. } else {
  508. if ($cookie instanceof Zend_Http_Cookie) {
  509. $name = $cookie->getName();
  510. $value = $cookie->getValue();
  511. $cookie = $name;
  512. }
  513. if (preg_match("/[=,; \t\r\n\013\014]/", $cookie)) {
  514. require_once 'Zend/Http/Client/Exception.php';
  515. throw new Zend_Http_Client_Exception("Cookie name cannot contain these characters: =,; \t\r\n\013\014 ({$cookie})");
  516. }
  517. $value = addslashes($value);
  518. if (! isset($this->headers['cookie'])) $this->headers['cookie'] = array('Cookie', '');
  519. $this->headers['cookie'][1] .= $cookie . '=' . $value . '; ';
  520. }
  521. return $this;
  522. }
  523. /**
  524. * Set a file to upload (using a POST request)
  525. *
  526. * Can be used in two ways:
  527. *
  528. * 1. $data is null (default): $filename is treated as the name if a local file which
  529. * will be read and sent. Will try to guess the content type using mime_content_type().
  530. * 2. $data is set - $filename is sent as the file name, but $data is sent as the file
  531. * contents and no file is read from the file system. In this case, you need to
  532. * manually set the content-type ($ctype) or it will default to
  533. * application/octet-stream.
  534. *
  535. * @param string $filename Name of file to upload, or name to save as
  536. * @param string $formname Name of form element to send as
  537. * @param string $data Data to send (if null, $filename is read and sent)
  538. * @param string $ctype Content type to use (if $data is set and $ctype is
  539. * null, will be application/octet-stream)
  540. * @return Zend_Http_Client
  541. */
  542. public function setFileUpload($filename, $formname, $data = null, $ctype = null)
  543. {
  544. if ($data === null) {
  545. if (($data = @file_get_contents($filename)) === false) {
  546. require_once 'Zend/Http/Client/Exception.php';
  547. throw new Zend_Http_Client_Exception("Unable to read file '{$filename}' for upload");
  548. }
  549. if (! $ctype && function_exists('mime_content_type')) $ctype = mime_content_type($filename);
  550. }
  551. // Force enctype to multipart/form-data
  552. $this->setEncType(self::ENC_FORMDATA);
  553. if ($ctype === null) $ctype = 'application/octet-stream';
  554. $this->files[$formname] = array(basename($filename), $ctype, $data);
  555. return $this;
  556. }
  557. /**
  558. * Set the encoding type for POST data
  559. *
  560. * @param string $enctype
  561. * @return Zend_Http_Client
  562. */
  563. public function setEncType($enctype = self::ENC_URLENCODED)
  564. {
  565. $this->enctype = $enctype;
  566. return $this;
  567. }
  568. /**
  569. * Set the raw (already encoded) POST data.
  570. *
  571. * This function is here for two reasons:
  572. * 1. For advanced user who would like to set their own data, already encoded
  573. * 2. For backwards compatibilty: If someone uses the old post($data) method.
  574. * this method will be used to set the encoded data.
  575. *
  576. * @param string $data
  577. * @param string $enctype
  578. * @return Zend_Http_Client
  579. */
  580. public function setRawData($data, $enctype = null)
  581. {
  582. $this->raw_post_data = $data;
  583. $this->setEncType($enctype);
  584. return $this;
  585. }
  586. /**
  587. * Clear all GET and POST parameters
  588. *
  589. * Should be used to reset the request parameters if the client is
  590. * used for several concurrent requests.
  591. *
  592. * @return Zend_Http_Client
  593. */
  594. public function resetParameters()
  595. {
  596. // Reset parameter data
  597. $this->paramsGet = array();
  598. $this->paramsPost = array();
  599. $this->files = array();
  600. $this->raw_post_data = null;
  601. // Clear outdated headers
  602. if (isset($this->headers['content-type'])) unset($this->headers['content-type']);
  603. if (isset($this->headers['content-length'])) unset($this->headers['content-length']);
  604. return $this;
  605. }
  606. /**
  607. * Get the last HTTP request as string
  608. *
  609. * @return string
  610. */
  611. public function getLastRequest()
  612. {
  613. return $this->last_request;
  614. }
  615. /**
  616. * Get the last HTTP response received by this client
  617. *
  618. * If $config['storeresponse'] is set to false, or no response was
  619. * stored yet, will return null
  620. *
  621. * @return Zend_Http_Response or null if none
  622. */
  623. public function getLastResponse()
  624. {
  625. return $this->last_response;
  626. }
  627. /**
  628. * Load the connection adapter
  629. *
  630. * While this method is not called more than one for a client, it is
  631. * seperated from ->request() to preserve logic and readability
  632. *
  633. * @param Zend_Http_Client_Adapter_Interface|string $adapter
  634. */
  635. public function setAdapter($adapter)
  636. {
  637. if (is_string($adapter)) {
  638. try {
  639. Zend_Loader::loadClass($adapter);
  640. } catch (Zend_Exception $e) {
  641. require_once 'Zend/Http/Client/Exception.php';
  642. throw new Zend_Http_Client_Exception("Unable to load adapter '$adapter': {$e->getMessage()}");
  643. }
  644. $adapter = new $adapter;
  645. }
  646. if (! $adapter instanceof Zend_Http_Client_Adapter_Interface) {
  647. require_once 'Zend/Http/Client/Exception.php';
  648. throw new Zend_Http_Client_Exception('Passed adapter is not a HTTP connection adapter');
  649. }
  650. $this->adapter = $adapter;
  651. $config = $this->config;
  652. unset($config['adapter']);
  653. $this->adapter->setConfig($config);
  654. }
  655. /**
  656. * Send the HTTP request and return an HTTP response object
  657. *
  658. * @param string $method
  659. * @return Zend_Http_Response
  660. */
  661. public function request($method = null)
  662. {
  663. if (! $this->uri instanceof Zend_Uri_Http) {
  664. require_once 'Zend/Http/Client/Exception.php';
  665. throw new Zend_Http_Client_Exception('No valid URI has been passed to the client');
  666. }
  667. if ($method) $this->setMethod($method);
  668. $this->redirectCounter = 0;
  669. $response = null;
  670. // Make sure the adapter is loaded
  671. if ($this->adapter == null) $this->setAdapter($this->config['adapter']);
  672. // Send the first request. If redirected, continue.
  673. do {
  674. // Clone the URI and add the additional GET parameters to it
  675. $uri = clone $this->uri;
  676. if (! empty($this->paramsGet)) {
  677. $query = $uri->getQuery();
  678. if (! empty($query)) $query .= '&';
  679. $query .= http_build_query($this->paramsGet, null, '&');
  680. $uri->setQuery($query);
  681. }
  682. $body = $this->prepare_body();
  683. $headers = $this->prepare_headers();
  684. // Open the connection, send the request and read the response
  685. $this->adapter->connect($uri->getHost(), $uri->getPort(),
  686. ($uri->getScheme() == 'https' ? true : false));
  687. $this->last_request = $this->adapter->write($this->method,
  688. $uri, $this->config['httpversion'], $headers, $body);
  689. $response = $this->adapter->read();
  690. /*if (! $response) {
  691. require_once 'Zend/Http/Client/Exception.php';
  692. throw new Zend_Http_Client_Exception('Unable to read response, or response is empty');
  693. }*/
  694. $response = Zend_Http_Response::fromString($response);
  695. if ($this->config['storeresponse']) $this->last_response = $response;
  696. // Load cookies into cookie jar
  697. if (isset($this->cookiejar)) $this->cookiejar->addCookiesFromResponse($response, $uri);
  698. // If we got redirected, look for the Location header
  699. if ($response->isRedirect() && ($location = $response->getHeader('location'))) {
  700. // Check whether we send the exact same request again, or drop the parameters
  701. // and send a GET request
  702. if ($response->getStatus() == 303 ||
  703. ((! $this->config['strictredirects']) && ($response->getStatus() == 302 ||
  704. $response->getStatus() == 301))) {
  705. $this->resetParameters();
  706. $this->setMethod(self::GET);
  707. }
  708. // If we got a well formed absolute URI
  709. if (Zend_Uri_Http::check($location)) {
  710. $this->setHeaders('host', null);
  711. $this->setUri($location);
  712. } else {
  713. // Split into path and query and set the query
  714. if (strpos($location, '?') !== false) {
  715. list($location, $query) = explode('?', $location, 2);
  716. } else {
  717. $query = '';
  718. }
  719. $this->uri->setQuery($query);
  720. // Else, if we got just an absolute path, set it
  721. if(strpos($location, '/') === 0) {
  722. $this->uri->setPath($location);
  723. // Else, assume we have a relative path
  724. } else {
  725. // Get the current path directory, removing any trailing slashes
  726. $path = $this->uri->getPath();
  727. $path = rtrim(substr($path, 0, strrpos($path, '/')), "/");
  728. $this->uri->setPath($path . '/' . $location);
  729. }
  730. }
  731. ++$this->redirectCounter;
  732. } else {
  733. // If we didn't get any location, stop redirecting
  734. break;
  735. }
  736. } while ($this->redirectCounter < $this->config['maxredirects']);
  737. return $response;
  738. }
  739. /**
  740. * Prepare the request headers
  741. *
  742. * @return array
  743. */
  744. protected function prepare_headers()
  745. {
  746. $headers = array();
  747. // Set the host header
  748. if (! isset($this->headers['host'])) {
  749. $host = $this->uri->getHost();
  750. // If the port is not default, add it
  751. if (! (($this->uri->getScheme() == 'http' && $this->uri->getPort() == 80) ||
  752. ($this->uri->getScheme() == 'https' && $this->uri->getPort() == 443))) {
  753. $host .= ':' . $this->uri->getPort();
  754. }
  755. $headers[] = "Host: {$host}";
  756. }
  757. // Set the connection header
  758. if (! isset($this->headers['connection'])) {
  759. if (! $this->config['keepalive']) $headers[] = "Connection: close";
  760. }
  761. // Set the Accept-encoding header if not set - depending on whether
  762. // zlib is available or not.
  763. if (! isset($this->headers['accept-encoding'])) {
  764. if (function_exists('gzinflate')) {
  765. $headers[] = 'Accept-encoding: gzip, deflate';
  766. } else {
  767. $headers[] = 'Accept-encoding: identity';
  768. }
  769. }
  770. // Set the content-type header
  771. if ($this->method == self::POST &&
  772. (! isset($this->headers['content-type']) && isset($this->enctype))) {
  773. $headers[] = "Content-type: {$this->enctype}";
  774. }
  775. // Set the user agent header
  776. if (! isset($this->headers['user-agent']) && isset($this->config['useragent'])) {
  777. $headers[] = "User-agent: {$this->config['useragent']}";
  778. }
  779. // Set HTTP authentication if needed
  780. if (is_array($this->auth)) {
  781. $auth = self::encodeAuthHeader($this->auth['user'], $this->auth['password'], $this->auth['type']);
  782. $headers[] = "Authorization: {$auth}";
  783. }
  784. // Load cookies from cookie jar
  785. if (isset($this->cookiejar)) {
  786. $cookstr = $this->cookiejar->getMatchingCookies($this->uri,
  787. true, Zend_Http_CookieJar::COOKIE_STRING_CONCAT);
  788. if ($cookstr) $headers[] = "Cookie: {$cookstr}";
  789. }
  790. // Add all other user defined headers
  791. foreach ($this->headers as $header) {
  792. list($name, $value) = $header;
  793. if (is_array($value))
  794. $value = implode(', ', $value);
  795. $headers[] = "$name: $value";
  796. }
  797. return $headers;
  798. }
  799. /**
  800. * Prepare the request body (for POST and PUT requests)
  801. *
  802. * @return string
  803. */
  804. protected function prepare_body()
  805. {
  806. // According to RFC2616, a TRACE request should not have a body.
  807. if ($this->method == self::TRACE) {
  808. return '';
  809. }
  810. // If we have raw_post_data set, just use it as the body.
  811. if (isset($this->raw_post_data)) {
  812. $this->setHeaders('Content-length', strlen($this->raw_post_data));
  813. return $this->raw_post_data;
  814. }
  815. $body = '';
  816. // If we have files to upload, force enctype to multipart/form-data
  817. if (count ($this->files) > 0) $this->setEncType(self::ENC_FORMDATA);
  818. // If we have POST parameters or files, encode and add them to the body
  819. if (count($this->paramsPost) > 0 || count($this->files) > 0) {
  820. switch($this->enctype) {
  821. case self::ENC_FORMDATA:
  822. // Encode body as multipart/form-data
  823. $boundary = '---ZENDHTTPCLIENT-' . md5(microtime());
  824. $this->setHeaders('Content-type', self::ENC_FORMDATA . "; boundary={$boundary}");
  825. // Get POST parameters and encode them
  826. $params = $this->_getParametersRecursive($this->paramsPost);
  827. foreach ($params as $pp) {
  828. $body .= self::encodeFormData($boundary, $pp[0], $pp[1]);
  829. }
  830. // Encode files
  831. foreach ($this->files as $name => $file) {
  832. $fhead = array('Content-type' => $file[1]);
  833. $body .= self::encodeFormData($boundary, $name, $file[2], $file[0], $fhead);
  834. }
  835. $body .= "--{$boundary}--\r\n";
  836. break;
  837. case self::ENC_URLENCODED:
  838. // Encode body as application/x-www-form-urlencoded
  839. $this->setHeaders('Content-type', self::ENC_URLENCODED);
  840. $body = http_build_query($this->paramsPost, '', '&');
  841. break;
  842. default:
  843. require_once 'Zend/Http/Client/Exception.php';
  844. throw new Zend_Http_Client_Exception("Cannot handle content type '{$this->enctype}' automatically." .
  845. " Please use Zend_Http_Client::setRawData to send this kind of content.");
  846. break;
  847. }
  848. }
  849. if ($body) $this->setHeaders('Content-length', strlen($body));
  850. return $body;
  851. }
  852. /**
  853. * Helper method that gets a possibly multi-level parameters array (get or
  854. * post) and flattens it.
  855. *
  856. * The method returns an array of (key, value) pairs (because keys are not
  857. * necessarily unique. If one of the parameters in as array, it will also
  858. * add a [] suffix to the key.
  859. *
  860. * @param array $parray The parameters array
  861. * @param bool $urlencode Whether to urlencode the name and value
  862. * @return array
  863. */
  864. protected function _getParametersRecursive($parray, $urlencode = false)
  865. {
  866. if (! is_array($parray)) return $parray;
  867. $parameters = array();
  868. foreach ($parray as $name => $value) {
  869. if ($urlencode) $name = urlencode($name);
  870. // If $value is an array, iterate over it
  871. if (is_array($value)) {
  872. $name .= ($urlencode ? '%5B%5D' : '[]');
  873. foreach ($value as $subval) {
  874. if ($urlencode) $subval = urlencode($subval);
  875. $parameters[] = array($name, $subval);
  876. }
  877. } else {
  878. if ($urlencode) $value = urlencode($value);
  879. $parameters[] = array($name, $value);
  880. }
  881. }
  882. return $parameters;
  883. }
  884. /**
  885. * Encode data to a multipart/form-data part suitable for a POST request.
  886. *
  887. * @param string $boundary
  888. * @param string $name
  889. * @param mixed $value
  890. * @param string $filename
  891. * @param array $headers Associative array of optional headers @example ("Content-transfer-encoding" => "binary")
  892. * @return string
  893. */
  894. public static function encodeFormData($boundary, $name, $value, $filename = null, $headers = array()) {
  895. $ret = "--{$boundary}\r\n" .
  896. 'Content-disposition: form-data; name="' . $name .'"';
  897. if ($filename) $ret .= '; filename="' . $filename . '"';
  898. $ret .= "\r\n";
  899. foreach ($headers as $hname => $hvalue) {
  900. $ret .= "{$hname}: {$hvalue}\r\n";
  901. }
  902. $ret .= "\r\n";
  903. $ret .= "{$value}\r\n";
  904. return $ret;
  905. }
  906. /**
  907. * Create a HTTP authentication "Authorization:" header according to the
  908. * specified user, password and authentication method.
  909. *
  910. * @see http://www.faqs.org/rfcs/rfc2617.html
  911. * @param string $user
  912. * @param string $password
  913. * @param string $type
  914. * @return string
  915. */
  916. public static function encodeAuthHeader($user, $password, $type = self::AUTH_BASIC)
  917. {
  918. $authHeader = null;
  919. switch ($type) {
  920. case self::AUTH_BASIC:
  921. // In basic authentication, the user name cannot contain ":"
  922. if (strpos($user, ':') !== false) {
  923. require_once 'Zend/Http/Client/Exception.php';
  924. throw new Zend_Http_Client_Exception("The user name cannot contain ':' in 'Basic' HTTP authentication");
  925. }
  926. $authHeader = 'Basic ' . base64_encode($user . ':' . $password);
  927. break;
  928. //case self::AUTH_DIGEST:
  929. /**
  930. * @todo Implement digest authentication
  931. */
  932. // break;
  933. default:
  934. require_once 'Zend/Http/Client/Exception.php';
  935. throw new Zend_Http_Client_Exception("Not a supported HTTP authentication type: '$type'");
  936. }
  937. return $authHeader;
  938. }
  939. }