]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - Zend/Http/Client/Adapter/Curl.php
Release 6.5.0
[Github/sugarcrm.git] / Zend / Http / Client / Adapter / Curl.php
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
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 }