8 * This source file is subject to the new BSD license that is bundled
9 * with this package in the file LICENSE.txt.
10 * It is also available through the world-wide-web at this URL:
11 * http://framework.zend.com/license/new-bsd
12 * If you did not receive a copy of the license and are unable to
13 * obtain it through the world-wide-web, please send an email
14 * to license@zend.com so we can send you a copy immediately.
18 * @subpackage Client_Adapter
20 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
21 * @license http://framework.zend.com/license/new-bsd New BSD License
27 require_once 'Zend/Uri/Http.php';
30 * @see Zend_Http_Client_Adapter_Interface
32 require_once 'Zend/Http/Client/Adapter/Interface.php';
34 * @see Zend_Http_Client_Adapter_Stream
36 require_once 'Zend/Http/Client/Adapter/Stream.php';
39 * An adapter class for Zend_Http_Client based on the curl extension.
40 * Curl requires libcurl. See for full requirements the PHP manual: http://php.net/curl
44 * @subpackage Client_Adapter
45 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
46 * @license http://framework.zend.com/license/new-bsd New BSD License
48 class Zend_Http_Client_Adapter_Curl implements Zend_Http_Client_Adapter_Interface, Zend_Http_Client_Adapter_Stream
55 protected $_config = array();
58 * What host/port are we connected to?
62 protected $_connected_to = array(null, null);
65 * The curl session handle
69 protected $_curl = null;
72 * List of cURL options that should never be overwritten
76 protected $_invalidOverwritableCurlOptions;
79 * Response gotten from server
83 protected $_response = null;
86 * Stream for storing output
90 protected $out_stream;
95 * Config is set using setConfig()
98 * @throws Zend_Http_Client_Adapter_Exception
100 public function __construct()
102 if (!extension_loaded('curl')) {
103 require_once 'Zend/Http/Client/Adapter/Exception.php';
104 throw new Zend_Http_Client_Adapter_Exception('cURL extension has to be loaded to use this Zend_Http_Client adapter.');
106 $this->_invalidOverwritableCurlOptions = array(
110 CURLOPT_CUSTOMREQUEST,
112 CURLOPT_RETURNTRANSFER,
119 CURLOPT_CONNECTTIMEOUT,
120 CURL_HTTP_VERSION_1_1,
121 CURL_HTTP_VERSION_1_0,
126 * Set the configuration array for the adapter
128 * @throws Zend_Http_Client_Adapter_Exception
129 * @param Zend_Config | array $config
130 * @return Zend_Http_Client_Adapter_Curl
132 public function setConfig($config = array())
134 if ($config instanceof Zend_Config) {
135 $config = $config->toArray();
137 } elseif (! is_array($config)) {
138 require_once 'Zend/Http/Client/Adapter/Exception.php';
139 throw new Zend_Http_Client_Adapter_Exception(
140 'Array or Zend_Config object expected, got ' . gettype($config)
144 if(isset($config['proxy_user']) && isset($config['proxy_pass'])) {
145 $this->setCurlOption(CURLOPT_PROXYUSERPWD, $config['proxy_user'].":".$config['proxy_pass']);
146 unset($config['proxy_user'], $config['proxy_pass']);
149 foreach ($config as $k => $v) {
150 $option = strtolower($k);
153 $this->setCurlOption(CURLOPT_PROXY, $v);
156 $this->setCurlOption(CURLOPT_PROXYPORT, $v);
159 $this->_config[$option] = $v;
168 * Retrieve the array of all configuration options
172 public function getConfig()
174 return $this->_config;
178 * Direct setter for cURL adapter related options.
180 * @param string|int $option
181 * @param mixed $value
182 * @return Zend_Http_Adapter_Curl
184 public function setCurlOption($option, $value)
186 if (!isset($this->_config['curloptions'])) {
187 $this->_config['curloptions'] = array();
189 $this->_config['curloptions'][$option] = $value;
196 * @param string $host
198 * @param boolean $secure
200 * @throws Zend_Http_Client_Adapter_Exception if unable to connect
202 public function connect($host, $port = 80, $secure = false)
204 // If we're already connected, disconnect first
209 // If we are connected to a different server or port, disconnect first
211 && is_array($this->_connected_to)
212 && ($this->_connected_to[0] != $host
213 || $this->_connected_to[1] != $port)
218 // Do the actual connection
219 $this->_curl = curl_init();
221 curl_setopt($this->_curl, CURLOPT_PORT, intval($port));
225 curl_setopt($this->_curl, CURLOPT_CONNECTTIMEOUT, $this->_config['timeout']);
228 curl_setopt($this->_curl, CURLOPT_MAXREDIRS, $this->_config['maxredirects']);
233 require_once 'Zend/Http/Client/Adapter/Exception.php';
234 throw new Zend_Http_Client_Adapter_Exception('Unable to Connect to ' . $host . ':' . $port);
237 if ($secure !== false) {
238 // Behave the same like Zend_Http_Adapter_Socket on SSL options.
239 if (isset($this->_config['sslcert'])) {
240 curl_setopt($this->_curl, CURLOPT_SSLCERT, $this->_config['sslcert']);
242 if (isset($this->_config['sslpassphrase'])) {
243 curl_setopt($this->_curl, CURLOPT_SSLCERTPASSWD, $this->_config['sslpassphrase']);
247 // Update connected_to
248 $this->_connected_to = array($host, $port);
252 * Send request to the remote server
254 * @param string $method
255 * @param Zend_Uri_Http $uri
256 * @param float $http_ver
257 * @param array $headers
258 * @param string $body
259 * @return string $request
260 * @throws Zend_Http_Client_Adapter_Exception If connection fails, connected to wrong host, no PUT file defined, unsupported method, or unsupported cURL option
262 public function write($method, $uri, $httpVersion = 1.1, $headers = array(), $body = '')
264 // Make sure we're properly connected
266 require_once 'Zend/Http/Client/Adapter/Exception.php';
267 throw new Zend_Http_Client_Adapter_Exception("Trying to write but we are not connected");
270 if ($this->_connected_to[0] != $uri->getHost() || $this->_connected_to[1] != $uri->getPort()) {
271 require_once 'Zend/Http/Client/Adapter/Exception.php';
272 throw new Zend_Http_Client_Adapter_Exception("Trying to write but we are connected to the wrong host");
276 curl_setopt($this->_curl, CURLOPT_URL, $uri->__toString());
278 // ensure correct curl call
281 case Zend_Http_Client::GET:
282 $curlMethod = CURLOPT_HTTPGET;
285 case Zend_Http_Client::POST:
286 $curlMethod = CURLOPT_POST;
289 case Zend_Http_Client::PUT:
290 // There are two different types of PUT request, either a Raw Data string has been set
291 // or CURLOPT_INFILE and CURLOPT_INFILESIZE are used.
292 if(is_resource($body)) {
293 $this->_config['curloptions'][CURLOPT_INFILE] = $body;
295 if (isset($this->_config['curloptions'][CURLOPT_INFILE])) {
296 // Now we will probably already have Content-Length set, so that we have to delete it
297 // from $headers at this point:
298 foreach ($headers AS $k => $header) {
299 if (preg_match('/Content-Length:\s*(\d+)/i', $header, $m)) {
300 if(is_resource($body)) {
301 $this->_config['curloptions'][CURLOPT_INFILESIZE] = (int)$m[1];
307 if (!isset($this->_config['curloptions'][CURLOPT_INFILESIZE])) {
308 require_once 'Zend/Http/Client/Adapter/Exception.php';
309 throw new Zend_Http_Client_Adapter_Exception("Cannot set a file-handle for cURL option CURLOPT_INFILE without also setting its size in CURLOPT_INFILESIZE.");
312 if(is_resource($body)) {
316 $curlMethod = CURLOPT_PUT;
318 $curlMethod = CURLOPT_CUSTOMREQUEST;
323 case Zend_Http_Client::DELETE:
324 $curlMethod = CURLOPT_CUSTOMREQUEST;
325 $curlValue = "DELETE";
328 case Zend_Http_Client::OPTIONS:
329 $curlMethod = CURLOPT_CUSTOMREQUEST;
330 $curlValue = "OPTIONS";
333 case Zend_Http_Client::TRACE:
334 $curlMethod = CURLOPT_CUSTOMREQUEST;
335 $curlValue = "TRACE";
338 case Zend_Http_Client::HEAD:
339 $curlMethod = CURLOPT_CUSTOMREQUEST;
344 // For now, through an exception for unsupported request methods
345 require_once 'Zend/Http/Client/Adapter/Exception.php';
346 throw new Zend_Http_Client_Adapter_Exception("Method currently not supported");
349 if(is_resource($body) && $curlMethod != CURLOPT_PUT) {
350 require_once 'Zend/Http/Client/Adapter/Exception.php';
351 throw new Zend_Http_Client_Adapter_Exception("Streaming requests are allowed only with PUT");
354 // get http version to use
355 $curlHttp = ($httpVersion == 1.1) ? CURL_HTTP_VERSION_1_1 : CURL_HTTP_VERSION_1_0;
357 // mark as HTTP request and set HTTP method
358 curl_setopt($this->_curl, $curlHttp, true);
359 curl_setopt($this->_curl, $curlMethod, $curlValue);
361 if($this->out_stream) {
362 // headers will be read into the response
363 curl_setopt($this->_curl, CURLOPT_HEADER, false);
364 curl_setopt($this->_curl, CURLOPT_HEADERFUNCTION, array($this, "readHeader"));
365 // and data will be written into the file
366 curl_setopt($this->_curl, CURLOPT_FILE, $this->out_stream);
368 // ensure headers are also returned
369 curl_setopt($this->_curl, CURLOPT_HEADER, true);
371 // ensure actual response is returned
372 curl_setopt($this->_curl, CURLOPT_RETURNTRANSFER, true);
375 // set additional headers
376 $headers['Accept'] = '';
377 curl_setopt($this->_curl, CURLOPT_HTTPHEADER, $headers);
380 * Make sure POSTFIELDS is set after $curlMethod is set:
381 * @link http://de2.php.net/manual/en/function.curl-setopt.php#81161
383 if ($method == Zend_Http_Client::POST) {
384 curl_setopt($this->_curl, CURLOPT_POSTFIELDS, $body);
385 } elseif ($curlMethod == CURLOPT_PUT) {
386 // this covers a PUT by file-handle:
387 // Make the setting of this options explicit (rather than setting it through the loop following a bit lower)
388 // to group common functionality together.
389 curl_setopt($this->_curl, CURLOPT_INFILE, $this->_config['curloptions'][CURLOPT_INFILE]);
390 curl_setopt($this->_curl, CURLOPT_INFILESIZE, $this->_config['curloptions'][CURLOPT_INFILESIZE]);
391 unset($this->_config['curloptions'][CURLOPT_INFILE]);
392 unset($this->_config['curloptions'][CURLOPT_INFILESIZE]);
393 } elseif ($method == Zend_Http_Client::PUT) {
394 // This is a PUT by a setRawData string, not by file-handle
395 curl_setopt($this->_curl, CURLOPT_POSTFIELDS, $body);
398 // set additional curl options
399 if (isset($this->_config['curloptions'])) {
400 foreach ((array)$this->_config['curloptions'] as $k => $v) {
401 if (!in_array($k, $this->_invalidOverwritableCurlOptions)) {
402 if (curl_setopt($this->_curl, $k, $v) == false) {
403 require_once 'Zend/Http/Client/Exception.php';
404 throw new Zend_Http_Client_Exception(sprintf("Unknown or erroreous cURL option '%s' set", $k));
411 $response = curl_exec($this->_curl);
413 // if we used streaming, headers are already there
414 if(!is_resource($this->out_stream)) {
415 $this->_response = $response;
418 $request = curl_getinfo($this->_curl, CURLINFO_HEADER_OUT);
421 if (empty($this->_response)) {
422 require_once 'Zend/Http/Client/Exception.php';
423 throw new Zend_Http_Client_Exception("Error in cURL request: " . curl_error($this->_curl));
426 // cURL automatically decodes chunked-messages, this means we have to disallow the Zend_Http_Response to do it again
427 if (stripos($this->_response, "Transfer-Encoding: chunked\r\n")) {
428 $this->_response = str_ireplace("Transfer-Encoding: chunked\r\n", '', $this->_response);
431 // Eliminate multiple HTTP responses.
433 $parts = preg_split('|(?:\r?\n){2}|m', $this->_response, 2);
436 if (isset($parts[1]) && preg_match("|^HTTP/1\.[01](.*?)\r\n|mi", $parts[1])) {
437 $this->_response = $parts[1];
442 // cURL automatically handles Proxy rewrites, remove the "HTTP/1.0 200 Connection established" string:
443 if (stripos($this->_response, "HTTP/1.0 200 Connection established\r\n\r\n") !== false) {
444 $this->_response = str_ireplace("HTTP/1.0 200 Connection established\r\n\r\n", '', $this->_response);
451 * Return read response from server
455 public function read()
457 return $this->_response;
461 * Close the connection to the server
464 public function close()
466 if(is_resource($this->_curl)) {
467 curl_close($this->_curl);
470 $this->_connected_to = array(null, null);
478 public function getHandle()
484 * Set output stream for the response
486 * @param resource $stream
487 * @return Zend_Http_Client_Adapter_Socket
489 public function setOutputStream($stream)
491 $this->out_stream = $stream;
496 * Header reader function for CURL
498 * @param resource $curl
499 * @param string $header
502 public function readHeader($curl, $header)
504 $this->_response .= $header;
505 return strlen($header);