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 }