web/lib/Zend/Http/Client/Adapter/Curl.php
changeset 64 162c1de6545a
parent 19 1c2f13fd785c
child 68 ecaf28ffe26e
equal deleted inserted replaced
63:5b37998e522e 64:162c1de6545a
       
     1 <?php
       
     2 
       
     3 /**
       
     4  * Zend Framework
       
     5  *
       
     6  * LICENSE
       
     7  *
       
     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.
       
    15  *
       
    16  * @category   Zend
       
    17  * @package    Zend_Http
       
    18  * @subpackage Client_Adapter
       
    19  * @version    $Id: Curl.php 22216 2010-05-20 21:12:05Z dragonbe $
       
    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
       
    22  */
       
    23 
       
    24 /**
       
    25  * @see Zend_Uri_Http
       
    26  */
       
    27 require_once 'Zend/Uri/Http.php';
       
    28 
       
    29 /**
       
    30  * @see Zend_Http_Client_Adapter_Interface
       
    31  */
       
    32 require_once 'Zend/Http/Client/Adapter/Interface.php';
       
    33 /**
       
    34  * @see Zend_Http_Client_Adapter_Stream
       
    35  */
       
    36 require_once 'Zend/Http/Client/Adapter/Stream.php';
       
    37 
       
    38 /**
       
    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
       
    41  *
       
    42  * @category   Zend
       
    43  * @package    Zend_Http
       
    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
       
    47  */
       
    48 class Zend_Http_Client_Adapter_Curl implements Zend_Http_Client_Adapter_Interface, Zend_Http_Client_Adapter_Stream
       
    49 {
       
    50     /**
       
    51      * Parameters array
       
    52      *
       
    53      * @var array
       
    54      */
       
    55     protected $_config = array();
       
    56 
       
    57     /**
       
    58      * What host/port are we connected to?
       
    59      *
       
    60      * @var array
       
    61      */
       
    62     protected $_connected_to = array(null, null);
       
    63 
       
    64     /**
       
    65      * The curl session handle
       
    66      *
       
    67      * @var resource|null
       
    68      */
       
    69     protected $_curl = null;
       
    70 
       
    71     /**
       
    72      * List of cURL options that should never be overwritten
       
    73      *
       
    74      * @var array
       
    75      */
       
    76     protected $_invalidOverwritableCurlOptions;
       
    77 
       
    78     /**
       
    79      * Response gotten from server
       
    80      *
       
    81      * @var string
       
    82      */
       
    83     protected $_response = null;
       
    84 
       
    85     /**
       
    86      * Stream for storing output
       
    87      *
       
    88      * @var resource
       
    89      */
       
    90     protected $out_stream;
       
    91 
       
    92     /**
       
    93      * Adapter constructor
       
    94      *
       
    95      * Config is set using setConfig()
       
    96      *
       
    97      * @return void
       
    98      * @throws Zend_Http_Client_Adapter_Exception
       
    99      */
       
   100     public function __construct()
       
   101     {
       
   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.');
       
   105         }
       
   106         $this->_invalidOverwritableCurlOptions = array(
       
   107             CURLOPT_HTTPGET,
       
   108             CURLOPT_POST,
       
   109             CURLOPT_PUT,
       
   110             CURLOPT_CUSTOMREQUEST,
       
   111             CURLOPT_HEADER,
       
   112             CURLOPT_RETURNTRANSFER,
       
   113             CURLOPT_HTTPHEADER,
       
   114             CURLOPT_POSTFIELDS,
       
   115             CURLOPT_INFILE,
       
   116             CURLOPT_INFILESIZE,
       
   117             CURLOPT_PORT,
       
   118             CURLOPT_MAXREDIRS,
       
   119             CURLOPT_CONNECTTIMEOUT,
       
   120             CURL_HTTP_VERSION_1_1,
       
   121             CURL_HTTP_VERSION_1_0,
       
   122         );
       
   123     }
       
   124 
       
   125     /**
       
   126      * Set the configuration array for the adapter
       
   127      *
       
   128      * @throws Zend_Http_Client_Adapter_Exception
       
   129      * @param  Zend_Config | array $config
       
   130      * @return Zend_Http_Client_Adapter_Curl
       
   131      */
       
   132     public function setConfig($config = array())
       
   133     {
       
   134         if ($config instanceof Zend_Config) {
       
   135             $config = $config->toArray();
       
   136 
       
   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)
       
   141             );
       
   142         }
       
   143 
       
   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']);
       
   147         }
       
   148 
       
   149         foreach ($config as $k => $v) {
       
   150             $option = strtolower($k);
       
   151             switch($option) {
       
   152                 case 'proxy_host':
       
   153                     $this->setCurlOption(CURLOPT_PROXY, $v);
       
   154                     break;
       
   155                 case 'proxy_port':
       
   156                     $this->setCurlOption(CURLOPT_PROXYPORT, $v);
       
   157                     break;
       
   158                 default:
       
   159                     $this->_config[$option] = $v;
       
   160                     break;
       
   161             }
       
   162         }
       
   163 
       
   164         return $this;
       
   165     }
       
   166 
       
   167     /**
       
   168       * Retrieve the array of all configuration options
       
   169       *
       
   170       * @return array
       
   171       */
       
   172      public function getConfig()
       
   173      {
       
   174          return $this->_config;
       
   175      }
       
   176 
       
   177     /**
       
   178      * Direct setter for cURL adapter related options.
       
   179      *
       
   180      * @param  string|int $option
       
   181      * @param  mixed $value
       
   182      * @return Zend_Http_Adapter_Curl
       
   183      */
       
   184     public function setCurlOption($option, $value)
       
   185     {
       
   186         if (!isset($this->_config['curloptions'])) {
       
   187             $this->_config['curloptions'] = array();
       
   188         }
       
   189         $this->_config['curloptions'][$option] = $value;
       
   190         return $this;
       
   191     }
       
   192 
       
   193     /**
       
   194      * Initialize curl
       
   195      *
       
   196      * @param  string  $host
       
   197      * @param  int     $port
       
   198      * @param  boolean $secure
       
   199      * @return void
       
   200      * @throws Zend_Http_Client_Adapter_Exception if unable to connect
       
   201      */
       
   202     public function connect($host, $port = 80, $secure = false)
       
   203     {
       
   204         // If we're already connected, disconnect first
       
   205         if ($this->_curl) {
       
   206             $this->close();
       
   207         }
       
   208 
       
   209         // If we are connected to a different server or port, disconnect first
       
   210         if ($this->_curl
       
   211             && is_array($this->_connected_to)
       
   212             && ($this->_connected_to[0] != $host
       
   213             || $this->_connected_to[1] != $port)
       
   214         ) {
       
   215             $this->close();
       
   216         }
       
   217 
       
   218         // Do the actual connection
       
   219         $this->_curl = curl_init();
       
   220         if ($port != 80) {
       
   221             curl_setopt($this->_curl, CURLOPT_PORT, intval($port));
       
   222         }
       
   223 
       
   224         // Set timeout
       
   225         curl_setopt($this->_curl, CURLOPT_CONNECTTIMEOUT, $this->_config['timeout']);
       
   226 
       
   227         // Set Max redirects
       
   228         curl_setopt($this->_curl, CURLOPT_MAXREDIRS, $this->_config['maxredirects']);
       
   229 
       
   230         if (!$this->_curl) {
       
   231             $this->close();
       
   232 
       
   233             require_once 'Zend/Http/Client/Adapter/Exception.php';
       
   234             throw new Zend_Http_Client_Adapter_Exception('Unable to Connect to ' .  $host . ':' . $port);
       
   235         }
       
   236 
       
   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']);
       
   241             }
       
   242             if (isset($this->_config['sslpassphrase'])) {
       
   243                 curl_setopt($this->_curl, CURLOPT_SSLCERTPASSWD, $this->_config['sslpassphrase']);
       
   244             }
       
   245         }
       
   246 
       
   247         // Update connected_to
       
   248         $this->_connected_to = array($host, $port);
       
   249     }
       
   250 
       
   251     /**
       
   252      * Send request to the remote server
       
   253      *
       
   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
       
   261      */
       
   262     public function write($method, $uri, $httpVersion = 1.1, $headers = array(), $body = '')
       
   263     {
       
   264         // Make sure we're properly connected
       
   265         if (!$this->_curl) {
       
   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");
       
   268         }
       
   269 
       
   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");
       
   273         }
       
   274 
       
   275         // set URL
       
   276         curl_setopt($this->_curl, CURLOPT_URL, $uri->__toString());
       
   277 
       
   278         // ensure correct curl call
       
   279         $curlValue = true;
       
   280         switch ($method) {
       
   281             case Zend_Http_Client::GET:
       
   282                 $curlMethod = CURLOPT_HTTPGET;
       
   283                 break;
       
   284 
       
   285             case Zend_Http_Client::POST:
       
   286                 $curlMethod = CURLOPT_POST;
       
   287                 break;
       
   288 
       
   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;
       
   294                 }
       
   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];
       
   302                             }
       
   303                             unset($headers[$k]);
       
   304                         }
       
   305                     }
       
   306 
       
   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.");
       
   310                     }
       
   311 
       
   312                     if(is_resource($body)) {
       
   313                         $body = '';
       
   314                     }
       
   315 
       
   316                     $curlMethod = CURLOPT_PUT;
       
   317                 } else {
       
   318                     $curlMethod = CURLOPT_CUSTOMREQUEST;
       
   319                     $curlValue = "PUT";
       
   320                 }
       
   321                 break;
       
   322 
       
   323             case Zend_Http_Client::DELETE:
       
   324                 $curlMethod = CURLOPT_CUSTOMREQUEST;
       
   325                 $curlValue = "DELETE";
       
   326                 break;
       
   327 
       
   328             case Zend_Http_Client::OPTIONS:
       
   329                 $curlMethod = CURLOPT_CUSTOMREQUEST;
       
   330                 $curlValue = "OPTIONS";
       
   331                 break;
       
   332 
       
   333             case Zend_Http_Client::TRACE:
       
   334                 $curlMethod = CURLOPT_CUSTOMREQUEST;
       
   335                 $curlValue = "TRACE";
       
   336                 break;
       
   337             
       
   338             case Zend_Http_Client::HEAD:
       
   339                 $curlMethod = CURLOPT_CUSTOMREQUEST;
       
   340                 $curlValue = "HEAD";
       
   341                 break;
       
   342 
       
   343             default:
       
   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");
       
   347         }
       
   348 
       
   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");
       
   352         }
       
   353 
       
   354         // get http version to use
       
   355         $curlHttp = ($httpVersion == 1.1) ? CURL_HTTP_VERSION_1_1 : CURL_HTTP_VERSION_1_0;
       
   356 
       
   357         // mark as HTTP request and set HTTP method
       
   358         curl_setopt($this->_curl, $curlHttp, true);
       
   359         curl_setopt($this->_curl, $curlMethod, $curlValue);
       
   360 
       
   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);
       
   367         } else {
       
   368             // ensure headers are also returned
       
   369             curl_setopt($this->_curl, CURLOPT_HEADER, true);
       
   370 
       
   371             // ensure actual response is returned
       
   372             curl_setopt($this->_curl, CURLOPT_RETURNTRANSFER, true);
       
   373         }
       
   374 
       
   375         // set additional headers
       
   376         $headers['Accept'] = '';
       
   377         curl_setopt($this->_curl, CURLOPT_HTTPHEADER, $headers);
       
   378 
       
   379         /**
       
   380          * Make sure POSTFIELDS is set after $curlMethod is set:
       
   381          * @link http://de2.php.net/manual/en/function.curl-setopt.php#81161
       
   382          */
       
   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);
       
   396         }
       
   397 
       
   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));
       
   405                     }
       
   406                 }
       
   407             }
       
   408         }
       
   409 
       
   410         // send the request
       
   411         $response = curl_exec($this->_curl);
       
   412 
       
   413         // if we used streaming, headers are already there
       
   414         if(!is_resource($this->out_stream)) {
       
   415             $this->_response = $response;
       
   416         }
       
   417 
       
   418         $request  = curl_getinfo($this->_curl, CURLINFO_HEADER_OUT);
       
   419         $request .= $body;
       
   420 
       
   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));
       
   424         }
       
   425 
       
   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);
       
   429         }
       
   430 
       
   431         // Eliminate multiple HTTP responses.
       
   432         do {
       
   433             $parts  = preg_split('|(?:\r?\n){2}|m', $this->_response, 2);
       
   434             $again  = false;
       
   435 
       
   436             if (isset($parts[1]) && preg_match("|^HTTP/1\.[01](.*?)\r\n|mi", $parts[1])) {
       
   437                 $this->_response    = $parts[1];
       
   438                 $again              = true;
       
   439             }
       
   440         } while ($again);
       
   441 
       
   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);
       
   445         }
       
   446 
       
   447         return $request;
       
   448     }
       
   449 
       
   450     /**
       
   451      * Return read response from server
       
   452      *
       
   453      * @return string
       
   454      */
       
   455     public function read()
       
   456     {
       
   457         return $this->_response;
       
   458     }
       
   459 
       
   460     /**
       
   461      * Close the connection to the server
       
   462      *
       
   463      */
       
   464     public function close()
       
   465     {
       
   466         if(is_resource($this->_curl)) {
       
   467             curl_close($this->_curl);
       
   468         }
       
   469         $this->_curl         = null;
       
   470         $this->_connected_to = array(null, null);
       
   471     }
       
   472 
       
   473     /**
       
   474      * Get cUrl Handle
       
   475      *
       
   476      * @return resource
       
   477      */
       
   478     public function getHandle()
       
   479     {
       
   480         return $this->_curl;
       
   481     }
       
   482 
       
   483     /**
       
   484      * Set output stream for the response
       
   485      *
       
   486      * @param resource $stream
       
   487      * @return Zend_Http_Client_Adapter_Socket
       
   488      */
       
   489     public function setOutputStream($stream)
       
   490     {
       
   491         $this->out_stream = $stream;
       
   492         return $this;
       
   493     }
       
   494 
       
   495     /**
       
   496      * Header reader function for CURL
       
   497      *
       
   498      * @param resource $curl
       
   499      * @param string $header
       
   500      * @return int
       
   501      */
       
   502     public function readHeader($curl, $header)
       
   503     {
       
   504         $this->_response .= $header;
       
   505         return strlen($header);
       
   506     }
       
   507 }