PageRenderTime 26ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/Zend/Http/Client/Adapter/Curl.php

https://bitbucket.org/blackriver/openx
PHP | 334 lines | 163 code | 37 blank | 134 comment | 52 complexity | 0f2b9b5f33b226482b8aa8cbf80a009e MD5 | raw file
  1. <?php
  2. //echo "<pre>";
  3. //debug_print_backtrace();
  4. //echo "</pre>";
  5. /**
  6. * Zend Framework
  7. *
  8. * LICENSE
  9. *
  10. * This source file is subject to the new BSD license that is bundled
  11. * with this package in the file LICENSE.txt.
  12. * It is also available through the world-wide-web at this URL:
  13. * http://framework.zend.com/license/new-bsd
  14. * If you did not receive a copy of the license and are unable to
  15. * obtain it through the world-wide-web, please send an email
  16. * to license@zend.com so we can send you a copy immediately.
  17. *
  18. * @category Zend
  19. * @package Zend_Http
  20. * @subpackage Client_Adapter
  21. * @version $Id: mergeCopyTarget55204.tmp 42386 2009-08-31 11:23:39Z lukasz.wikierski $
  22. * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  23. * @license http://framework.zend.com/license/new-bsd New BSD License
  24. */
  25. require_once 'Zend/Uri/Http.php';
  26. require_once 'Zend/Http/Client/Adapter/Interface.php';
  27. require_once 'Zend/Http/Client/Adapter/Exception.php';
  28. /**
  29. * An adapter class for Zend_Http_Client based on the curl extension.
  30. * Curl requires libcurl. See for full requirements the PHP manual: http://php.net/curl
  31. *
  32. * @category Zend
  33. * @package Zend_Http
  34. * @subpackage Client_Adapter
  35. * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  36. * @license http://framework.zend.com/license/new-bsd New BSD License
  37. */
  38. class Zend_Http_Client_Adapter_Curl implements Zend_Http_Client_Adapter_Interface
  39. {
  40. /**
  41. * The curl session handle
  42. *
  43. * @var resource|null
  44. */
  45. protected $curl = null;
  46. /**
  47. * What host/port are we connected to?
  48. *
  49. * @var array
  50. */
  51. protected $connected_to = array(null, null);
  52. /**
  53. * Parameters array
  54. *
  55. * @var array
  56. */
  57. protected $config = array();
  58. /**
  59. * Response gotten from server
  60. *
  61. * @var string
  62. */
  63. protected $response = null;
  64. /**
  65. * Adapter constructor, currently empty. Config is set using setConfig()
  66. * @throws Zend_Http_Client_Adapter_Exception
  67. */
  68. public function __construct()
  69. {
  70. if (extension_loaded('curl') === false) {
  71. throw new Zend_Http_Client_Adapter_Exception('cURL extension has to be loaded to use this Zend_Http_Client adapter.');
  72. }
  73. }
  74. /**
  75. * Set the configuration array for the adapter
  76. *
  77. * @throws Zend_Http_Client_Adapter_Exception
  78. * @param array $config
  79. */
  80. public function setConfig($config = array())
  81. {
  82. if (! is_array($config)) {
  83. throw new Zend_Http_Client_Adapter_Exception('Http Adapter configuration expects an array, ' . gettype($config) . ' recieved.');
  84. }
  85. foreach ($config as $k => $v) {
  86. $this->config[strtolower($k)] = $v;
  87. }
  88. }
  89. /**
  90. * Direct setter for cURL adapter related options.
  91. *
  92. * @param string|int $option
  93. * @param mixed $value
  94. * @return Zend_Http_Adapter_Curl
  95. */
  96. public function setCurlOption($option, $value)
  97. {
  98. if(!isset($this->config['curloptions'])) {
  99. $this->config['curloptions'] = array();
  100. }
  101. $this->config['curloptions'][$option] = $value;
  102. return $this;
  103. }
  104. /**
  105. * Initialize curl
  106. *
  107. * @param string $host
  108. * @param int $port
  109. * @param boolean $secure
  110. */
  111. public function connect($host, $port = 80, $secure = false)
  112. {
  113. // If we're already connected, disconnect first
  114. if ($this->curl) $this->close();
  115. // If we are connected to a different server or port, disconnect first
  116. if ($this->curl && is_array($this->connected_to) &&
  117. ($this->connected_to[0] != $host || $this->connected_to[1] != $port))
  118. $this->close();
  119. // Do the actual connection
  120. $this->curl = curl_init();
  121. if ($port != 80) {
  122. curl_setopt($this->curl, CURLOPT_PORT, intval($port));
  123. }
  124. // Set timeout
  125. curl_setopt($this->curl, CURLOPT_TIMEOUT, $this->config['timeout']);
  126. // Set Max redirects
  127. curl_setopt($this->curl, CURLOPT_MAXREDIRS, $this->config['maxredirects']);
  128. if (! $this->curl) {
  129. $this->close();
  130. throw new Zend_Http_Client_Adapter_Exception('Unable to Connect to ' .
  131. $host . ':' . $port);
  132. }
  133. if($secure == true) {
  134. // Behave the same like Zend_Http_Adapter_Socket on SSL options.
  135. if($this->config['sslcert'] != null) {
  136. curl_setopt($this->curl, CURLOPT_SSLCERT, $this->config['sslcert']);
  137. }
  138. if($this->config['sslpassphrase'] !== null) {
  139. curl_setopt($this->curl, CURLOPT_SSLCERTPASSWD, $this->config['sslpassphrase']);
  140. }
  141. }
  142. // Update connected_to
  143. $this->connected_to = array($host, $port);
  144. }
  145. /**
  146. * Send request to the remote server
  147. *
  148. * @param string $method
  149. * @param Zend_Uri_Http $uri
  150. * @param float $http_ver
  151. * @param array $headers
  152. * @param string $body
  153. * @return string $request
  154. */
  155. public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '')
  156. {
  157. $invalidOverwritableCurlOptions = array(
  158. CURLOPT_HTTPGET, CURLOPT_POST, CURLOPT_PUT, CURLOPT_CUSTOMREQUEST, CURLOPT_HEADER,
  159. CURLOPT_RETURNTRANSFER, CURLOPT_HTTPHEADER, CURLOPT_POSTFIELDS, CURLOPT_INFILE,
  160. CURLOPT_INFILESIZE, CURLOPT_PORT, CURLOPT_MAXREDIRS, CURLOPT_TIMEOUT,
  161. CURL_HTTP_VERSION_1_1, CURL_HTTP_VERSION_1_0
  162. );
  163. // set URL
  164. curl_setopt($this->curl, CURLOPT_URL, $uri->__toString());
  165. // Make sure we're properly connected
  166. if (! $this->curl)
  167. throw new Zend_Http_Client_Adapter_Exception("Trying to write but we are not connected");
  168. if ($this->connected_to[0] != $uri->getHost() || $this->connected_to[1] != $uri->getPort())
  169. throw new Zend_Http_Client_Adapter_Exception("Trying to write but we are connected to the wrong host");
  170. // ensure correct curl call
  171. $curlValue = true;
  172. if ($method == Zend_Http_Client::GET) {
  173. $curlMethod = CURLOPT_HTTPGET;
  174. } elseif ($method == Zend_Http_Client::POST) {
  175. $curlMethod = CURLOPT_POST;
  176. } elseif($method == Zend_Http_Client::PUT) {
  177. // There are two different types of PUT request, either a Raw Data string has been set
  178. // or CURLOPT_INFILE and CURLOPT_INFILESIZE are used.
  179. if(isset($this->config['curloptions'][CURLOPT_INFILE])) {
  180. if(!isset($this->config['curloptions'][CURLOPT_INFILESIZE])) {
  181. throw new Zend_Http_Client_Exception("Cannot set a file-handle for cURL option CURLOPT_INFILE without also setting its size in CURLOPT_INFILESIZE.");
  182. }
  183. // Now we will probably already have Content-Length set, so that we have to delete it
  184. // from $headers at this point:
  185. foreach($headers AS $k => $header) {
  186. if(stristr($header, "Content-Length:") !== false) {
  187. unset($headers[$k]);
  188. }
  189. }
  190. $curlMethod = CURLOPT_PUT;
  191. } else {
  192. $curlMethod = CURLOPT_CUSTOMREQUEST;
  193. $curlValue = "PUT";
  194. }
  195. } elseif($method == Zend_Http_Client::DELETE) {
  196. $curlMethod = CURLOPT_CUSTOMREQUEST;
  197. $curlValue = "DELETE";
  198. } elseif($method == Zend_Http_Client::OPTIONS) {
  199. $curlMethod = CURLOPT_CUSTOMREQUEST;
  200. $curlValue = "OPTIONS";
  201. } elseif($method == Zend_Http_Client::TRACE) {
  202. $curlMethod = CURLOPT_CUSTOMREQUEST;
  203. $curlValue = "TRACE";
  204. } else {
  205. // For now, through an exception for unsupported request methods
  206. throw new Zend_Http_Client_Adapter_Exception("Method currently not supported");
  207. }
  208. // get http version to use
  209. $curlHttp = ($http_ver = 1.1) ? CURL_HTTP_VERSION_1_1 : CURL_HTTP_VERSION_1_0;
  210. curl_setopt($this->curl, $curlMethod, $curlValue);
  211. curl_setopt($this->curl, $curlHttp, true);
  212. // ensure headers are also returned
  213. curl_setopt($this->curl, CURLOPT_HEADER, true);
  214. // ensure actual response is returned
  215. curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true);
  216. // set additional headers
  217. $headers['Accept'] = "";
  218. curl_setopt($this->curl, CURLOPT_HTTPHEADER, $headers);
  219. /**
  220. * Make sure POSTFIELDS is set after $curlMethod is set:
  221. * @link http://de2.php.net/manual/en/function.curl-setopt.php#81161
  222. */
  223. if ($method == Zend_Http_Client::POST) {
  224. curl_setopt($this->curl, CURLOPT_POSTFIELDS, $body);
  225. } else if($curlMethod == CURLOPT_PUT) {
  226. // this covers a PUT by file-handle:
  227. // Make the setting of this options explicit (rather than setting it through the loop following a bit lower)
  228. // to group common functionality together.
  229. curl_setopt($this->curl, CURLOPT_INFILE, $this->config['curloptions'][CURLOPT_INFILE]);
  230. curl_setopt($this->curl, CURLOPT_INFILESIZE, $this->config['curloptions'][CURLOPT_INFILESIZE]);
  231. unset($this->config['curloptions'][CURLOPT_INFILE]);
  232. unset($this->config['curloptions'][CURLOPT_INFILESIZE]);
  233. } else if($method == Zend_Http_Client::PUT) {
  234. // This is a PUT by a setRawData string, not by file-handle
  235. curl_setopt($this->curl, CURLOPT_POSTFIELDS, $body);
  236. }
  237. // set additional curl options
  238. if(isset($this->config['curloptions'])) {
  239. foreach((array)$this->config['curloptions'] AS $k => $v) {
  240. if(!in_array($k, $invalidOverwritableCurlOptions)) {
  241. if(curl_setopt($this->curl, $k, $v) == false) {
  242. throw new Zend_Http_Client_Exception(sprintf("Unknown or erroreous cURL option '%s' set", $k));
  243. }
  244. }
  245. }
  246. }
  247. // send the request
  248. $this->response = curl_exec($this->curl);
  249. $request = curl_getinfo($this->curl, CURLINFO_HEADER_OUT);
  250. $request .= $body;
  251. if(empty($this->response)) {
  252. throw new Zend_Http_Client_Exception("Error in cURL request: ".curl_error($this->curl));
  253. }
  254. // cURL automatically decodes chunked-messages, this means we have to disallow the Zend_Http_Response to do it again
  255. if(stripos($this->response, "Transfer-Encoding: chunked\r\n")) {
  256. $this->response = str_ireplace("Transfer-Encoding: chunked\r\n", '', $this->response);
  257. }
  258. // TODO: Probably the pattern for multiple handshake requests is always the same, several HTTP codes in the response. Use that information?
  259. // cURL automactically handles Expect: 100-continue; and its responses. Delete the HTTP 100 CONTINUE from a response
  260. // because it messes up Zend_Http_Response parsing
  261. if(stripos($this->response, "HTTP/1.1 100 Continue\r\n\r\n") !== false) {
  262. $this->response = str_ireplace("HTTP/1.1 100 Continue\r\n\r\n", '', $this->response);
  263. }
  264. // cURL automatically handles Proxy rewrites, remove the "HTTP/1.0 200 Connection established" string:
  265. if(stripos($this->response, "HTTP/1.0 200 Connection established\r\n\r\n") !== false) {
  266. $this->response = str_ireplace("HTTP/1.0 200 Connection established\r\n\r\n", '', $this->response);
  267. }
  268. return $request;
  269. }
  270. /**
  271. * Return read response from server
  272. *
  273. * @return string
  274. */
  275. public function read()
  276. {
  277. return $this->response;
  278. }
  279. /**
  280. * Close the connection to the server
  281. *
  282. */
  283. public function close()
  284. {
  285. curl_close($this->curl);
  286. $this->curl = null;
  287. $this->connected_to = array(null, null);
  288. }
  289. /**
  290. * Destructor: make sure curl is disconnected
  291. *
  292. */
  293. public function __destruct()
  294. {
  295. if ($this->curl) $this->close();
  296. }
  297. }