File indexing completed on 2024-12-29 05:27:45
0001 <?php 0002 0003 /** 0004 * Zend Framework 0005 * 0006 * LICENSE 0007 * 0008 * This source file is subject to the new BSD license that is bundled 0009 * with this package in the file LICENSE.txt. 0010 * It is also available through the world-wide-web at this URL: 0011 * http://framework.zend.com/license/new-bsd 0012 * If you did not receive a copy of the license and are unable to 0013 * obtain it through the world-wide-web, please send an email 0014 * to license@zend.com so we can send you a copy immediately. 0015 * 0016 * @category Zend 0017 * @package Zend_Http 0018 * @subpackage Client_Adapter 0019 * @version $Id$ 0020 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0021 * @license http://framework.zend.com/license/new-bsd New BSD License 0022 */ 0023 0024 /** 0025 * @see Zend_Uri_Http 0026 */ 0027 // require_once 'Zend/Uri/Http.php'; 0028 0029 /** 0030 * @see Zend_Http_Client_Adapter_Interface 0031 */ 0032 // require_once 'Zend/Http/Client/Adapter/Interface.php'; 0033 /** 0034 * @see Zend_Http_Client_Adapter_Stream 0035 */ 0036 // require_once 'Zend/Http/Client/Adapter/Stream.php'; 0037 0038 /** 0039 * An adapter class for Zend_Http_Client based on the curl extension. 0040 * Curl requires libcurl. See for full requirements the PHP manual: http://php.net/curl 0041 * 0042 * @category Zend 0043 * @package Zend_Http 0044 * @subpackage Client_Adapter 0045 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0046 * @license http://framework.zend.com/license/new-bsd New BSD License 0047 */ 0048 class Zend_Http_Client_Adapter_Curl implements Zend_Http_Client_Adapter_Interface, Zend_Http_Client_Adapter_Stream 0049 { 0050 /** 0051 * Parameters array 0052 * 0053 * @var array 0054 */ 0055 protected $_config = array(); 0056 0057 /** 0058 * What host/port are we connected to? 0059 * 0060 * @var array 0061 */ 0062 protected $_connected_to = array(null, null); 0063 0064 /** 0065 * The curl session handle 0066 * 0067 * @var resource|null 0068 */ 0069 protected $_curl = null; 0070 0071 /** 0072 * List of cURL options that should never be overwritten 0073 * 0074 * @var array 0075 */ 0076 protected $_invalidOverwritableCurlOptions; 0077 0078 /** 0079 * Response gotten from server 0080 * 0081 * @var string 0082 */ 0083 protected $_response = null; 0084 0085 /** 0086 * Stream for storing output 0087 * 0088 * @var resource 0089 */ 0090 protected $out_stream; 0091 0092 /** 0093 * Adapter constructor 0094 * 0095 * Config is set using setConfig() 0096 * 0097 * @return void 0098 * @throws Zend_Http_Client_Adapter_Exception 0099 */ 0100 public function __construct() 0101 { 0102 if (!extension_loaded('curl')) { 0103 // require_once 'Zend/Http/Client/Adapter/Exception.php'; 0104 throw new Zend_Http_Client_Adapter_Exception('cURL extension has to be loaded to use this Zend_Http_Client adapter.'); 0105 } 0106 $this->_invalidOverwritableCurlOptions = array( 0107 CURLOPT_HTTPGET, 0108 CURLOPT_POST, 0109 CURLOPT_PUT, 0110 CURLOPT_CUSTOMREQUEST, 0111 CURLOPT_HEADER, 0112 CURLOPT_RETURNTRANSFER, 0113 CURLOPT_HTTPHEADER, 0114 CURLOPT_POSTFIELDS, 0115 CURLOPT_INFILE, 0116 CURLOPT_INFILESIZE, 0117 CURLOPT_PORT, 0118 CURLOPT_MAXREDIRS, 0119 CURLOPT_CONNECTTIMEOUT, 0120 CURL_HTTP_VERSION_1_1, 0121 CURL_HTTP_VERSION_1_0, 0122 ); 0123 } 0124 0125 /** 0126 * Set the configuration array for the adapter 0127 * 0128 * @throws Zend_Http_Client_Adapter_Exception 0129 * @param Zend_Config | array $config 0130 * @return Zend_Http_Client_Adapter_Curl 0131 */ 0132 public function setConfig($config = array()) 0133 { 0134 if ($config instanceof Zend_Config) { 0135 $config = $config->toArray(); 0136 0137 } elseif (! is_array($config)) { 0138 // require_once 'Zend/Http/Client/Adapter/Exception.php'; 0139 throw new Zend_Http_Client_Adapter_Exception( 0140 'Array or Zend_Config object expected, got ' . gettype($config) 0141 ); 0142 } 0143 0144 if(isset($config['proxy_user']) && isset($config['proxy_pass'])) { 0145 $this->setCurlOption(CURLOPT_PROXYUSERPWD, $config['proxy_user'].":".$config['proxy_pass']); 0146 unset($config['proxy_user'], $config['proxy_pass']); 0147 } 0148 0149 foreach ($config as $k => $v) { 0150 $option = strtolower($k); 0151 switch($option) { 0152 case 'proxy_host': 0153 $this->setCurlOption(CURLOPT_PROXY, $v); 0154 break; 0155 case 'proxy_port': 0156 $this->setCurlOption(CURLOPT_PROXYPORT, $v); 0157 break; 0158 default: 0159 $this->_config[$option] = $v; 0160 break; 0161 } 0162 } 0163 0164 return $this; 0165 } 0166 0167 /** 0168 * Retrieve the array of all configuration options 0169 * 0170 * @return array 0171 */ 0172 public function getConfig() 0173 { 0174 return $this->_config; 0175 } 0176 0177 /** 0178 * Direct setter for cURL adapter related options. 0179 * 0180 * @param string|int $option 0181 * @param mixed $value 0182 * @return Zend_Http_Adapter_Curl 0183 */ 0184 public function setCurlOption($option, $value) 0185 { 0186 if (!isset($this->_config['curloptions'])) { 0187 $this->_config['curloptions'] = array(); 0188 } 0189 $this->_config['curloptions'][$option] = $value; 0190 return $this; 0191 } 0192 0193 /** 0194 * Initialize curl 0195 * 0196 * @param string $host 0197 * @param int $port 0198 * @param boolean $secure 0199 * @return void 0200 * @throws Zend_Http_Client_Adapter_Exception if unable to connect 0201 */ 0202 public function connect($host, $port = 80, $secure = false) 0203 { 0204 // If we're already connected, disconnect first 0205 if ($this->_curl) { 0206 $this->close(); 0207 } 0208 0209 // If we are connected to a different server or port, disconnect first 0210 if ($this->_curl 0211 && is_array($this->_connected_to) 0212 && ($this->_connected_to[0] != $host 0213 || $this->_connected_to[1] != $port) 0214 ) { 0215 $this->close(); 0216 } 0217 0218 // Do the actual connection 0219 $this->_curl = curl_init(); 0220 if ($port != 80) { 0221 curl_setopt($this->_curl, CURLOPT_PORT, intval($port)); 0222 } 0223 0224 // Set connection timeout 0225 $connectTimeout = $this->_config['timeout']; 0226 $constant = CURLOPT_CONNECTTIMEOUT; 0227 if (defined('CURLOPT_CONNECTTIMEOUT_MS')) { 0228 $connectTimeout *= 1000; 0229 $constant = constant('CURLOPT_CONNECTTIMEOUT_MS'); 0230 } 0231 curl_setopt($this->_curl, $constant, $connectTimeout); 0232 0233 // Set request timeout (once connection is established) 0234 if (array_key_exists('request_timeout', $this->_config)) { 0235 $requestTimeout = $this->_config['request_timeout']; 0236 $constant = CURLOPT_TIMEOUT; 0237 if (defined('CURLOPT_TIMEOUT_MS')) { 0238 $requestTimeout *= 1000; 0239 $constant = constant('CURLOPT_TIMEOUT_MS'); 0240 } 0241 curl_setopt($this->_curl, $constant, $requestTimeout); 0242 } 0243 0244 // Set Max redirects 0245 curl_setopt($this->_curl, CURLOPT_MAXREDIRS, $this->_config['maxredirects']); 0246 0247 if (!$this->_curl) { 0248 $this->close(); 0249 0250 // require_once 'Zend/Http/Client/Adapter/Exception.php'; 0251 throw new Zend_Http_Client_Adapter_Exception('Unable to Connect to ' . $host . ':' . $port); 0252 } 0253 0254 if ($secure !== false) { 0255 // Behave the same like Zend_Http_Adapter_Socket on SSL options. 0256 if (isset($this->_config['sslcert'])) { 0257 curl_setopt($this->_curl, CURLOPT_SSLCERT, $this->_config['sslcert']); 0258 } 0259 if (isset($this->_config['sslpassphrase'])) { 0260 curl_setopt($this->_curl, CURLOPT_SSLCERTPASSWD, $this->_config['sslpassphrase']); 0261 } 0262 } 0263 0264 // Update connected_to 0265 $this->_connected_to = array($host, $port); 0266 } 0267 0268 /** 0269 * Send request to the remote server 0270 * 0271 * @param string $method 0272 * @param Zend_Uri_Http $uri 0273 * @param float $http_ver 0274 * @param array $headers 0275 * @param string $body 0276 * @return string $request 0277 * @throws Zend_Http_Client_Adapter_Exception If connection fails, connected to wrong host, no PUT file defined, unsupported method, or unsupported cURL option 0278 */ 0279 public function write($method, $uri, $httpVersion = 1.1, $headers = array(), $body = '') 0280 { 0281 // Make sure we're properly connected 0282 if (!$this->_curl) { 0283 // require_once 'Zend/Http/Client/Adapter/Exception.php'; 0284 throw new Zend_Http_Client_Adapter_Exception("Trying to write but we are not connected"); 0285 } 0286 0287 if ($this->_connected_to[0] != $uri->getHost() || $this->_connected_to[1] != $uri->getPort()) { 0288 // require_once 'Zend/Http/Client/Adapter/Exception.php'; 0289 throw new Zend_Http_Client_Adapter_Exception("Trying to write but we are connected to the wrong host"); 0290 } 0291 0292 // set URL 0293 curl_setopt($this->_curl, CURLOPT_URL, $uri->__toString()); 0294 0295 // ensure correct curl call 0296 $curlValue = true; 0297 switch ($method) { 0298 case Zend_Http_Client::GET: 0299 $curlMethod = CURLOPT_HTTPGET; 0300 break; 0301 0302 case Zend_Http_Client::POST: 0303 $curlMethod = CURLOPT_POST; 0304 break; 0305 0306 case Zend_Http_Client::PUT: 0307 // There are two different types of PUT request, either a Raw Data string has been set 0308 // or CURLOPT_INFILE and CURLOPT_INFILESIZE are used. 0309 if(is_resource($body)) { 0310 $this->_config['curloptions'][CURLOPT_INFILE] = $body; 0311 } 0312 if (isset($this->_config['curloptions'][CURLOPT_INFILE])) { 0313 // Now we will probably already have Content-Length set, so that we have to delete it 0314 // from $headers at this point: 0315 foreach ($headers AS $k => $header) { 0316 if (preg_match('/Content-Length:\s*(\d+)/i', $header, $m)) { 0317 if(is_resource($body)) { 0318 $this->_config['curloptions'][CURLOPT_INFILESIZE] = (int)$m[1]; 0319 } 0320 unset($headers[$k]); 0321 } 0322 } 0323 0324 if (!isset($this->_config['curloptions'][CURLOPT_INFILESIZE])) { 0325 // require_once 'Zend/Http/Client/Adapter/Exception.php'; 0326 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."); 0327 } 0328 0329 if(is_resource($body)) { 0330 $body = ''; 0331 } 0332 0333 $curlMethod = CURLOPT_PUT; 0334 } else { 0335 $curlMethod = CURLOPT_CUSTOMREQUEST; 0336 $curlValue = "PUT"; 0337 } 0338 break; 0339 0340 case Zend_Http_Client::PATCH: 0341 $curlMethod = CURLOPT_CUSTOMREQUEST; 0342 $curlValue = "PATCH"; 0343 break; 0344 0345 case Zend_Http_Client::DELETE: 0346 $curlMethod = CURLOPT_CUSTOMREQUEST; 0347 $curlValue = "DELETE"; 0348 break; 0349 0350 case Zend_Http_Client::OPTIONS: 0351 $curlMethod = CURLOPT_CUSTOMREQUEST; 0352 $curlValue = "OPTIONS"; 0353 break; 0354 0355 case Zend_Http_Client::TRACE: 0356 $curlMethod = CURLOPT_CUSTOMREQUEST; 0357 $curlValue = "TRACE"; 0358 break; 0359 0360 case Zend_Http_Client::HEAD: 0361 $curlMethod = CURLOPT_CUSTOMREQUEST; 0362 $curlValue = "HEAD"; 0363 break; 0364 0365 default: 0366 // For now, through an exception for unsupported request methods 0367 // require_once 'Zend/Http/Client/Adapter/Exception.php'; 0368 throw new Zend_Http_Client_Adapter_Exception("Method currently not supported"); 0369 } 0370 0371 if(is_resource($body) && $curlMethod != CURLOPT_PUT) { 0372 // require_once 'Zend/Http/Client/Adapter/Exception.php'; 0373 throw new Zend_Http_Client_Adapter_Exception("Streaming requests are allowed only with PUT"); 0374 } 0375 0376 // get http version to use 0377 $curlHttp = ($httpVersion == 1.1) ? CURL_HTTP_VERSION_1_1 : CURL_HTTP_VERSION_1_0; 0378 0379 // mark as HTTP request and set HTTP method 0380 curl_setopt($this->_curl, CURLOPT_HTTP_VERSION, $curlHttp); 0381 curl_setopt($this->_curl, $curlMethod, $curlValue); 0382 0383 if($this->out_stream) { 0384 // headers will be read into the response 0385 curl_setopt($this->_curl, CURLOPT_HEADER, false); 0386 curl_setopt($this->_curl, CURLOPT_HEADERFUNCTION, array($this, "readHeader")); 0387 // and data will be written into the file 0388 curl_setopt($this->_curl, CURLOPT_FILE, $this->out_stream); 0389 } else { 0390 // ensure headers are also returned 0391 curl_setopt($this->_curl, CURLOPT_HEADER, true); 0392 curl_setopt($this->_curl, CURLINFO_HEADER_OUT, true); 0393 0394 // ensure actual response is returned 0395 curl_setopt($this->_curl, CURLOPT_RETURNTRANSFER, true); 0396 } 0397 0398 // set additional headers 0399 $headers['Accept'] = ''; 0400 curl_setopt($this->_curl, CURLOPT_HTTPHEADER, $headers); 0401 0402 /** 0403 * Make sure POSTFIELDS is set after $curlMethod is set: 0404 * @link http://de2.php.net/manual/en/function.curl-setopt.php#81161 0405 */ 0406 if ($method == Zend_Http_Client::POST) { 0407 curl_setopt($this->_curl, CURLOPT_POSTFIELDS, $body); 0408 } elseif ($curlMethod == CURLOPT_PUT) { 0409 // this covers a PUT by file-handle: 0410 // Make the setting of this options explicit (rather than setting it through the loop following a bit lower) 0411 // to group common functionality together. 0412 curl_setopt($this->_curl, CURLOPT_INFILE, $this->_config['curloptions'][CURLOPT_INFILE]); 0413 curl_setopt($this->_curl, CURLOPT_INFILESIZE, $this->_config['curloptions'][CURLOPT_INFILESIZE]); 0414 unset($this->_config['curloptions'][CURLOPT_INFILE]); 0415 unset($this->_config['curloptions'][CURLOPT_INFILESIZE]); 0416 } elseif ($method == Zend_Http_Client::PUT) { 0417 // This is a PUT by a setRawData string, not by file-handle 0418 curl_setopt($this->_curl, CURLOPT_POSTFIELDS, $body); 0419 } elseif ($method == Zend_Http_Client::PATCH) { 0420 // This is a PATCH by a setRawData string 0421 curl_setopt($this->_curl, CURLOPT_POSTFIELDS, $body); 0422 } elseif ($method == Zend_Http_Client::DELETE) { 0423 // This is a DELETE by a setRawData string 0424 curl_setopt($this->_curl, CURLOPT_POSTFIELDS, $body); 0425 } elseif ($method == Zend_Http_Client::OPTIONS) { 0426 // This is an OPTIONS by a setRawData string 0427 curl_setopt($this->_curl, CURLOPT_POSTFIELDS, $body); 0428 } 0429 0430 // set additional curl options 0431 if (isset($this->_config['curloptions'])) { 0432 foreach ((array)$this->_config['curloptions'] as $k => $v) { 0433 if (!in_array($k, $this->_invalidOverwritableCurlOptions)) { 0434 if (curl_setopt($this->_curl, $k, $v) == false) { 0435 // require_once 'Zend/Http/Client/Exception.php'; 0436 throw new Zend_Http_Client_Exception(sprintf("Unknown or erroreous cURL option '%s' set", $k)); 0437 } 0438 } 0439 } 0440 } 0441 0442 // send the request 0443 $response = curl_exec($this->_curl); 0444 0445 // if we used streaming, headers are already there 0446 if(!is_resource($this->out_stream)) { 0447 $this->_response = $response; 0448 } 0449 0450 $request = curl_getinfo($this->_curl, CURLINFO_HEADER_OUT); 0451 $request .= $body; 0452 0453 if (empty($this->_response)) { 0454 // require_once 'Zend/Http/Client/Exception.php'; 0455 throw new Zend_Http_Client_Exception("Error in cURL request: " . curl_error($this->_curl)); 0456 } 0457 0458 // cURL automatically decodes chunked-messages, this means we have to disallow the Zend_Http_Response to do it again 0459 if (stripos($this->_response, "Transfer-Encoding: chunked\r\n")) { 0460 $this->_response = str_ireplace("Transfer-Encoding: chunked\r\n", '', $this->_response); 0461 } 0462 0463 // Eliminate multiple HTTP responses. 0464 do { 0465 $parts = preg_split('|(?:\r?\n){2}|m', $this->_response, 2); 0466 $again = false; 0467 0468 if (isset($parts[1]) && preg_match("|^HTTP/1\.[01](.*?)\r\n|mi", $parts[1])) { 0469 $this->_response = $parts[1]; 0470 $again = true; 0471 } 0472 } while ($again); 0473 0474 // cURL automatically handles Proxy rewrites, remove the "HTTP/1.0 200 Connection established" string: 0475 if (stripos($this->_response, "HTTP/1.0 200 Connection established\r\n\r\n") !== false) { 0476 $this->_response = str_ireplace("HTTP/1.0 200 Connection established\r\n\r\n", '', $this->_response); 0477 } 0478 0479 return $request; 0480 } 0481 0482 /** 0483 * Return read response from server 0484 * 0485 * @return string 0486 */ 0487 public function read() 0488 { 0489 return $this->_response; 0490 } 0491 0492 /** 0493 * Close the connection to the server 0494 * 0495 */ 0496 public function close() 0497 { 0498 if(is_resource($this->_curl)) { 0499 curl_close($this->_curl); 0500 } 0501 $this->_curl = null; 0502 $this->_connected_to = array(null, null); 0503 } 0504 0505 /** 0506 * Get cUrl Handle 0507 * 0508 * @return resource 0509 */ 0510 public function getHandle() 0511 { 0512 return $this->_curl; 0513 } 0514 0515 /** 0516 * Set output stream for the response 0517 * 0518 * @param resource $stream 0519 * @return Zend_Http_Client_Adapter_Socket 0520 */ 0521 public function setOutputStream($stream) 0522 { 0523 $this->out_stream = $stream; 0524 return $this; 0525 } 0526 0527 /** 0528 * Header reader function for CURL 0529 * 0530 * @param resource $curl 0531 * @param string $header 0532 * @return int 0533 */ 0534 public function readHeader($curl, $header) 0535 { 0536 $this->_response .= $header; 0537 return strlen($header); 0538 } 0539 }