web/lib/Zend/Http/Client/Adapter/Proxy.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: Proxy.php 21792 2010-04-08 00:27:06Z stas $
       
    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  * @see Zend_Http_Client
       
    30  */
       
    31 require_once 'Zend/Http/Client.php';
       
    32 /**
       
    33  * @see Zend_Http_Client_Adapter_Socket
       
    34  */
       
    35 require_once 'Zend/Http/Client/Adapter/Socket.php';
       
    36 
       
    37 /**
       
    38  * HTTP Proxy-supporting Zend_Http_Client adapter class, based on the default
       
    39  * socket based adapter.
       
    40  *
       
    41  * Should be used if proxy HTTP access is required. If no proxy is set, will
       
    42  * fall back to Zend_Http_Client_Adapter_Socket behavior. Just like the
       
    43  * default Socket adapter, this adapter does not require any special extensions
       
    44  * installed.
       
    45  *
       
    46  * @category   Zend
       
    47  * @package    Zend_Http
       
    48  * @subpackage Client_Adapter
       
    49  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    50  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    51  */
       
    52 class Zend_Http_Client_Adapter_Proxy extends Zend_Http_Client_Adapter_Socket
       
    53 {
       
    54     /**
       
    55      * Parameters array
       
    56      *
       
    57      * @var array
       
    58      */
       
    59     protected $config = array(
       
    60         'ssltransport'  => 'ssl',
       
    61         'sslcert'       => null,
       
    62         'sslpassphrase' => null,
       
    63         'sslusecontext' => false,
       
    64         'proxy_host'    => '',
       
    65         'proxy_port'    => 8080,
       
    66         'proxy_user'    => '',
       
    67         'proxy_pass'    => '',
       
    68         'proxy_auth'    => Zend_Http_Client::AUTH_BASIC,
       
    69         'persistent'    => false
       
    70     );
       
    71 
       
    72     /**
       
    73      * Whether HTTPS CONNECT was already negotiated with the proxy or not
       
    74      *
       
    75      * @var boolean
       
    76      */
       
    77     protected $negotiated = false;
       
    78 
       
    79     /**
       
    80      * Connect to the remote server
       
    81      *
       
    82      * Will try to connect to the proxy server. If no proxy was set, will
       
    83      * fall back to the target server (behave like regular Socket adapter)
       
    84      *
       
    85      * @param string  $host
       
    86      * @param int     $port
       
    87      * @param boolean $secure
       
    88      */
       
    89     public function connect($host, $port = 80, $secure = false)
       
    90     {
       
    91         // If no proxy is set, fall back to Socket adapter
       
    92         if (! $this->config['proxy_host']) {
       
    93             return parent::connect($host, $port, $secure);
       
    94         }
       
    95         
       
    96         /* Url might require stream context even if proxy connection doesn't */
       
    97         if ($secure) {
       
    98         	$this->config['sslusecontext'] = true;
       
    99         }
       
   100 
       
   101         // Connect (a non-secure connection) to the proxy server
       
   102         return parent::connect(
       
   103             $this->config['proxy_host'],
       
   104             $this->config['proxy_port'],
       
   105             false
       
   106         );
       
   107     }
       
   108 
       
   109     /**
       
   110      * Send request to the proxy server
       
   111      *
       
   112      * @param string        $method
       
   113      * @param Zend_Uri_Http $uri
       
   114      * @param string        $http_ver
       
   115      * @param array         $headers
       
   116      * @param string        $body
       
   117      * @return string Request as string
       
   118      */
       
   119     public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '')
       
   120     {
       
   121         // If no proxy is set, fall back to default Socket adapter
       
   122         if (! $this->config['proxy_host']) return parent::write($method, $uri, $http_ver, $headers, $body);
       
   123 
       
   124         // Make sure we're properly connected
       
   125         if (! $this->socket) {
       
   126             require_once 'Zend/Http/Client/Adapter/Exception.php';
       
   127             throw new Zend_Http_Client_Adapter_Exception("Trying to write but we are not connected");
       
   128         }
       
   129 
       
   130         $host = $this->config['proxy_host'];
       
   131         $port = $this->config['proxy_port'];
       
   132 
       
   133         if ($this->connected_to[0] != "tcp://$host" || $this->connected_to[1] != $port) {
       
   134             require_once 'Zend/Http/Client/Adapter/Exception.php';
       
   135             throw new Zend_Http_Client_Adapter_Exception("Trying to write but we are connected to the wrong proxy server");
       
   136         }
       
   137 
       
   138         // Add Proxy-Authorization header
       
   139         if ($this->config['proxy_user'] && ! isset($headers['proxy-authorization'])) {
       
   140             $headers['proxy-authorization'] = Zend_Http_Client::encodeAuthHeader(
       
   141                 $this->config['proxy_user'], $this->config['proxy_pass'], $this->config['proxy_auth']
       
   142             );
       
   143         }
       
   144 
       
   145         // if we are proxying HTTPS, preform CONNECT handshake with the proxy
       
   146         if ($uri->getScheme() == 'https' && (! $this->negotiated)) {
       
   147             $this->connectHandshake($uri->getHost(), $uri->getPort(), $http_ver, $headers);
       
   148             $this->negotiated = true;
       
   149         }
       
   150 
       
   151         // Save request method for later
       
   152         $this->method = $method;
       
   153 
       
   154         // Build request headers
       
   155         if ($this->negotiated) {
       
   156             $path = $uri->getPath();
       
   157             if ($uri->getQuery()) {
       
   158                 $path .= '?' . $uri->getQuery();
       
   159             }
       
   160             $request = "$method $path HTTP/$http_ver\r\n";
       
   161         } else {
       
   162             $request = "$method $uri HTTP/$http_ver\r\n";
       
   163         }
       
   164 
       
   165         // Add all headers to the request string
       
   166         foreach ($headers as $k => $v) {
       
   167             if (is_string($k)) $v = "$k: $v";
       
   168             $request .= "$v\r\n";
       
   169         }
       
   170 
       
   171         if(is_resource($body)) {
       
   172             $request .= "\r\n";
       
   173         } else {
       
   174             // Add the request body
       
   175             $request .= "\r\n" . $body;
       
   176         }
       
   177         
       
   178         // Send the request
       
   179         if (! @fwrite($this->socket, $request)) {
       
   180             require_once 'Zend/Http/Client/Adapter/Exception.php';
       
   181             throw new Zend_Http_Client_Adapter_Exception("Error writing request to proxy server");
       
   182         }
       
   183         
       
   184         if(is_resource($body)) {
       
   185             if(stream_copy_to_stream($body, $this->socket) == 0) {
       
   186                 require_once 'Zend/Http/Client/Adapter/Exception.php';
       
   187                 throw new Zend_Http_Client_Adapter_Exception('Error writing request to server');
       
   188             }
       
   189         }
       
   190         
       
   191         return $request;
       
   192     }
       
   193 
       
   194     /**
       
   195      * Preform handshaking with HTTPS proxy using CONNECT method
       
   196      *
       
   197      * @param string  $host
       
   198      * @param integer $port
       
   199      * @param string  $http_ver
       
   200      * @param array   $headers
       
   201      */
       
   202     protected function connectHandshake($host, $port = 443, $http_ver = '1.1', array &$headers = array())
       
   203     {
       
   204         $request = "CONNECT $host:$port HTTP/$http_ver\r\n" .
       
   205                    "Host: " . $this->config['proxy_host'] . "\r\n";
       
   206 
       
   207         // Add the user-agent header
       
   208         if (isset($this->config['useragent'])) {
       
   209             $request .= "User-agent: " . $this->config['useragent'] . "\r\n";
       
   210         }
       
   211 
       
   212         // If the proxy-authorization header is set, send it to proxy but remove
       
   213         // it from headers sent to target host
       
   214         if (isset($headers['proxy-authorization'])) {
       
   215             $request .= "Proxy-authorization: " . $headers['proxy-authorization'] . "\r\n";
       
   216             unset($headers['proxy-authorization']);
       
   217         }
       
   218 
       
   219         $request .= "\r\n";
       
   220 
       
   221         // Send the request
       
   222         if (! @fwrite($this->socket, $request)) {
       
   223             require_once 'Zend/Http/Client/Adapter/Exception.php';
       
   224             throw new Zend_Http_Client_Adapter_Exception("Error writing request to proxy server");
       
   225         }
       
   226 
       
   227         // Read response headers only
       
   228         $response = '';
       
   229         $gotStatus = false;
       
   230         while ($line = @fgets($this->socket)) {
       
   231             $gotStatus = $gotStatus || (strpos($line, 'HTTP') !== false);
       
   232             if ($gotStatus) {
       
   233                 $response .= $line;
       
   234                 if (!chop($line)) break;
       
   235             }
       
   236         }
       
   237 
       
   238         // Check that the response from the proxy is 200
       
   239         if (Zend_Http_Response::extractCode($response) != 200) {
       
   240             require_once 'Zend/Http/Client/Adapter/Exception.php';
       
   241             throw new Zend_Http_Client_Adapter_Exception("Unable to connect to HTTPS proxy. Server response: " . $response);
       
   242         }
       
   243 
       
   244         // If all is good, switch socket to secure mode. We have to fall back
       
   245         // through the different modes
       
   246         $modes = array(
       
   247             STREAM_CRYPTO_METHOD_TLS_CLIENT,
       
   248             STREAM_CRYPTO_METHOD_SSLv3_CLIENT,
       
   249             STREAM_CRYPTO_METHOD_SSLv23_CLIENT,
       
   250             STREAM_CRYPTO_METHOD_SSLv2_CLIENT
       
   251         );
       
   252 
       
   253         $success = false;
       
   254         foreach($modes as $mode) {
       
   255             $success = stream_socket_enable_crypto($this->socket, true, $mode);
       
   256             if ($success) break;
       
   257         }
       
   258 
       
   259         if (! $success) {
       
   260                 require_once 'Zend/Http/Client/Adapter/Exception.php';
       
   261                 throw new Zend_Http_Client_Adapter_Exception("Unable to connect to" .
       
   262                     " HTTPS server through proxy: could not negotiate secure connection.");
       
   263         }
       
   264     }
       
   265 
       
   266     /**
       
   267      * Close the connection to the server
       
   268      *
       
   269      */
       
   270     public function close()
       
   271     {
       
   272         parent::close();
       
   273         $this->negotiated = false;
       
   274     }
       
   275 
       
   276     /**
       
   277      * Destructor: make sure the socket is disconnected
       
   278      *
       
   279      */
       
   280     public function __destruct()
       
   281     {
       
   282         if ($this->socket) $this->close();
       
   283     }
       
   284 }