web/lib/Zend/Http/Client/Adapter/Proxy.php
changeset 886 1e110b03ae96
parent 807 877f952ae2bd
child 1230 68c69c656a2c
equal deleted inserted replaced
885:2251fb41dbc7 886:1e110b03ae96
    14  * to license@zend.com so we can send you a copy immediately.
    14  * to license@zend.com so we can send you a copy immediately.
    15  *
    15  *
    16  * @category   Zend
    16  * @category   Zend
    17  * @package    Zend_Http
    17  * @package    Zend_Http
    18  * @subpackage Client_Adapter
    18  * @subpackage Client_Adapter
    19  * @version    $Id: Proxy.php 21792 2010-04-08 00:27:06Z stas $
    19  * @version    $Id: Proxy.php 25273 2013-03-06 08:02:21Z frosch $
    20  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
    20  * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
    21  * @license    http://framework.zend.com/license/new-bsd     New BSD License
    21  * @license    http://framework.zend.com/license/new-bsd     New BSD License
    22  */
    22  */
    23 
    23 
    24 /**
    24 /**
    25  * @see Zend_Uri_Http
    25  * @see Zend_Uri_Http
    44  * installed.
    44  * installed.
    45  *
    45  *
    46  * @category   Zend
    46  * @category   Zend
    47  * @package    Zend_Http
    47  * @package    Zend_Http
    48  * @subpackage Client_Adapter
    48  * @subpackage Client_Adapter
    49  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
    49  * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
    50  * @license    http://framework.zend.com/license/new-bsd     New BSD License
    50  * @license    http://framework.zend.com/license/new-bsd     New BSD License
    51  */
    51  */
    52 class Zend_Http_Client_Adapter_Proxy extends Zend_Http_Client_Adapter_Socket
    52 class Zend_Http_Client_Adapter_Proxy extends Zend_Http_Client_Adapter_Socket
    53 {
    53 {
    54     /**
    54     /**
    64         'proxy_host'    => '',
    64         'proxy_host'    => '',
    65         'proxy_port'    => 8080,
    65         'proxy_port'    => 8080,
    66         'proxy_user'    => '',
    66         'proxy_user'    => '',
    67         'proxy_pass'    => '',
    67         'proxy_pass'    => '',
    68         'proxy_auth'    => Zend_Http_Client::AUTH_BASIC,
    68         'proxy_auth'    => Zend_Http_Client::AUTH_BASIC,
    69         'persistent'    => false
    69         'persistent'    => false,
    70     );
    70     );
    71 
    71 
    72     /**
    72     /**
    73      * Whether HTTPS CONNECT was already negotiated with the proxy or not
    73      * Whether HTTPS CONNECT was already negotiated with the proxy or not
    74      *
    74      *
    75      * @var boolean
    75      * @var boolean
    76      */
    76      */
    77     protected $negotiated = false;
    77     protected $negotiated = false;
       
    78     
       
    79     /**
       
    80      * Stores the last CONNECT handshake request
       
    81      * 
       
    82      * @var string
       
    83      */
       
    84     protected $connectHandshakeRequest;
    78 
    85 
    79     /**
    86     /**
    80      * Connect to the remote server
    87      * Connect to the remote server
    81      *
    88      *
    82      * Will try to connect to the proxy server. If no proxy was set, will
    89      * Will try to connect to the proxy server. If no proxy was set, will
    87      * @param boolean $secure
    94      * @param boolean $secure
    88      */
    95      */
    89     public function connect($host, $port = 80, $secure = false)
    96     public function connect($host, $port = 80, $secure = false)
    90     {
    97     {
    91         // If no proxy is set, fall back to Socket adapter
    98         // If no proxy is set, fall back to Socket adapter
    92         if (! $this->config['proxy_host']) {
    99         if (!$this->config['proxy_host']) {
    93             return parent::connect($host, $port, $secure);
   100             return parent::connect($host, $port, $secure);
    94         }
   101         }
    95         
   102 
    96         /* Url might require stream context even if proxy connection doesn't */
   103         /* Url might require stream context even if proxy connection doesn't */
    97         if ($secure) {
   104         if ($secure) {
    98         	$this->config['sslusecontext'] = true;
   105             $this->config['sslusecontext'] = true;
    99         }
   106         }
   100 
   107 
   101         // Connect (a non-secure connection) to the proxy server
   108         // Connect (a non-secure connection) to the proxy server
   102         return parent::connect(
   109         return parent::connect(
   103             $this->config['proxy_host'],
   110             $this->config['proxy_host'],
   113      * @param Zend_Uri_Http $uri
   120      * @param Zend_Uri_Http $uri
   114      * @param string        $http_ver
   121      * @param string        $http_ver
   115      * @param array         $headers
   122      * @param array         $headers
   116      * @param string        $body
   123      * @param string        $body
   117      * @return string Request as string
   124      * @return string Request as string
   118      */
   125      * @throws Zend_Http_Client_Adapter_Exception
   119     public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '')
   126      */
       
   127     public function write(
       
   128         $method, $uri, $http_ver = '1.1', $headers = array(), $body = ''
       
   129     )
   120     {
   130     {
   121         // If no proxy is set, fall back to default Socket adapter
   131         // 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);
   132         if (!$this->config['proxy_host']) {
       
   133             return parent::write($method, $uri, $http_ver, $headers, $body);
       
   134         }
   123 
   135 
   124         // Make sure we're properly connected
   136         // Make sure we're properly connected
   125         if (! $this->socket) {
   137         if (!$this->socket) {
   126             require_once 'Zend/Http/Client/Adapter/Exception.php';
   138             require_once 'Zend/Http/Client/Adapter/Exception.php';
   127             throw new Zend_Http_Client_Adapter_Exception("Trying to write but we are not connected");
   139             throw new Zend_Http_Client_Adapter_Exception(
       
   140                 'Trying to write but we are not connected'
       
   141             );
   128         }
   142         }
   129 
   143 
   130         $host = $this->config['proxy_host'];
   144         $host = $this->config['proxy_host'];
   131         $port = $this->config['proxy_port'];
   145         $port = $this->config['proxy_port'];
   132 
   146 
   133         if ($this->connected_to[0] != "tcp://$host" || $this->connected_to[1] != $port) {
   147         if ($this->connected_to[0] != "tcp://$host"
   134             require_once 'Zend/Http/Client/Adapter/Exception.php';
   148             || $this->connected_to[1] != $port
   135             throw new Zend_Http_Client_Adapter_Exception("Trying to write but we are connected to the wrong proxy server");
   149         ) {
       
   150             require_once 'Zend/Http/Client/Adapter/Exception.php';
       
   151             throw new Zend_Http_Client_Adapter_Exception(
       
   152                 'Trying to write but we are connected to the wrong proxy server'
       
   153             );
   136         }
   154         }
   137 
   155 
   138         // Add Proxy-Authorization header
   156         // Add Proxy-Authorization header
   139         if ($this->config['proxy_user'] && ! isset($headers['proxy-authorization'])) {
   157         if ($this->config['proxy_user']) {
   140             $headers['proxy-authorization'] = Zend_Http_Client::encodeAuthHeader(
   158             // Check to see if one already exists
   141                 $this->config['proxy_user'], $this->config['proxy_pass'], $this->config['proxy_auth']
   159             $hasProxyAuthHeader = false;
   142             );
   160             foreach ($headers as $k => $v) {
       
   161                 if ((string) $k == 'proxy-authorization'
       
   162                     || preg_match("/^proxy-authorization:/i", $v)
       
   163                 ) {
       
   164                     $hasProxyAuthHeader = true;
       
   165                     break;
       
   166                 }
       
   167             }
       
   168             if (!$hasProxyAuthHeader) {
       
   169                 $headers[] = 'Proxy-authorization: '
       
   170                     . Zend_Http_Client::encodeAuthHeader(
       
   171                         $this->config['proxy_user'],
       
   172                         $this->config['proxy_pass'], $this->config['proxy_auth']
       
   173                     );
       
   174             }
   143         }
   175         }
   144 
   176 
   145         // if we are proxying HTTPS, preform CONNECT handshake with the proxy
   177         // if we are proxying HTTPS, preform CONNECT handshake with the proxy
   146         if ($uri->getScheme() == 'https' && (! $this->negotiated)) {
   178         if ($uri->getScheme() == 'https' && (!$this->negotiated)) {
   147             $this->connectHandshake($uri->getHost(), $uri->getPort(), $http_ver, $headers);
   179             $this->connectHandshake(
       
   180                 $uri->getHost(), $uri->getPort(), $http_ver, $headers
       
   181             );
   148             $this->negotiated = true;
   182             $this->negotiated = true;
   149         }
   183         }
   150 
   184 
   151         // Save request method for later
   185         // Save request method for later
   152         $this->method = $method;
   186         $this->method = $method;
   172             $request .= "\r\n";
   206             $request .= "\r\n";
   173         } else {
   207         } else {
   174             // Add the request body
   208             // Add the request body
   175             $request .= "\r\n" . $body;
   209             $request .= "\r\n" . $body;
   176         }
   210         }
   177         
   211 
   178         // Send the request
   212         // Send the request
   179         if (! @fwrite($this->socket, $request)) {
   213         if (!@fwrite($this->socket, $request)) {
   180             require_once 'Zend/Http/Client/Adapter/Exception.php';
   214             require_once 'Zend/Http/Client/Adapter/Exception.php';
   181             throw new Zend_Http_Client_Adapter_Exception("Error writing request to proxy server");
   215             throw new Zend_Http_Client_Adapter_Exception(
   182         }
   216                 'Error writing request to proxy server'
   183         
   217             );
       
   218         }
       
   219 
   184         if(is_resource($body)) {
   220         if(is_resource($body)) {
   185             if(stream_copy_to_stream($body, $this->socket) == 0) {
   221             if(stream_copy_to_stream($body, $this->socket) == 0) {
   186                 require_once 'Zend/Http/Client/Adapter/Exception.php';
   222                 require_once 'Zend/Http/Client/Adapter/Exception.php';
   187                 throw new Zend_Http_Client_Adapter_Exception('Error writing request to server');
   223                 throw new Zend_Http_Client_Adapter_Exception(
   188             }
   224                     'Error writing request to server'
   189         }
   225                 );
   190         
   226             }
       
   227         }
       
   228 
   191         return $request;
   229         return $request;
   192     }
   230     }
   193 
   231 
   194     /**
   232     /**
   195      * Preform handshaking with HTTPS proxy using CONNECT method
   233      * Preform handshaking with HTTPS proxy using CONNECT method
   196      *
   234      *
   197      * @param string  $host
   235      * @param string  $host
   198      * @param integer $port
   236      * @param integer $port
   199      * @param string  $http_ver
   237      * @param string  $http_ver
   200      * @param array   $headers
   238      * @param array   $headers
   201      */
   239      * @return void
   202     protected function connectHandshake($host, $port = 443, $http_ver = '1.1', array &$headers = array())
   240      * @throws Zend_Http_Client_Adapter_Exception
       
   241      */
       
   242     protected function connectHandshake(
       
   243         $host, $port = 443, $http_ver = '1.1', array &$headers = array()
       
   244     )
   203     {
   245     {
   204         $request = "CONNECT $host:$port HTTP/$http_ver\r\n" .
   246         $request = "CONNECT $host:$port HTTP/$http_ver\r\n" .
   205                    "Host: " . $this->config['proxy_host'] . "\r\n";
   247                    "Host: " . $this->config['proxy_host'] . "\r\n";
   206 
   248 
   207         // Add the user-agent header
   249         // Process provided headers, including important ones to CONNECT request
   208         if (isset($this->config['useragent'])) {
   250         foreach ($headers as $k => $v) {
   209             $request .= "User-agent: " . $this->config['useragent'] . "\r\n";
   251             switch (strtolower(substr($v,0,strpos($v,':')))) {
   210         }
   252                 case 'proxy-authorization':
   211 
   253                     // break intentionally omitted
   212         // If the proxy-authorization header is set, send it to proxy but remove
   254 
   213         // it from headers sent to target host
   255                 case 'user-agent':
   214         if (isset($headers['proxy-authorization'])) {
   256                     $request .= $v . "\r\n";
   215             $request .= "Proxy-authorization: " . $headers['proxy-authorization'] . "\r\n";
   257                     break;
   216             unset($headers['proxy-authorization']);
   258 
   217         }
   259                 default:
   218 
   260                     break;
       
   261             }
       
   262         }
   219         $request .= "\r\n";
   263         $request .= "\r\n";
       
   264         
       
   265         // @see ZF-3189
       
   266         $this->connectHandshakeRequest = $request;
   220 
   267 
   221         // Send the request
   268         // Send the request
   222         if (! @fwrite($this->socket, $request)) {
   269         if (!@fwrite($this->socket, $request)) {
   223             require_once 'Zend/Http/Client/Adapter/Exception.php';
   270             require_once 'Zend/Http/Client/Adapter/Exception.php';
   224             throw new Zend_Http_Client_Adapter_Exception("Error writing request to proxy server");
   271             throw new Zend_Http_Client_Adapter_Exception(
       
   272                 'Error writing request to proxy server'
       
   273             );
   225         }
   274         }
   226 
   275 
   227         // Read response headers only
   276         // Read response headers only
   228         $response = '';
   277         $response = '';
   229         $gotStatus = false;
   278         $gotStatus = false;
   230         while ($line = @fgets($this->socket)) {
   279         while ($line = @fgets($this->socket)) {
   231             $gotStatus = $gotStatus || (strpos($line, 'HTTP') !== false);
   280             $gotStatus = $gotStatus || (strpos($line, 'HTTP') !== false);
   232             if ($gotStatus) {
   281             if ($gotStatus) {
   233                 $response .= $line;
   282                 $response .= $line;
   234                 if (!chop($line)) break;
   283                 if (!chop($line)) {
       
   284                     break;
       
   285                 }
   235             }
   286             }
   236         }
   287         }
   237 
   288 
   238         // Check that the response from the proxy is 200
   289         // Check that the response from the proxy is 200
   239         if (Zend_Http_Response::extractCode($response) != 200) {
   290         if (Zend_Http_Response::extractCode($response) != 200) {
   240             require_once 'Zend/Http/Client/Adapter/Exception.php';
   291             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);
   292             throw new Zend_Http_Client_Adapter_Exception(
       
   293                 'Unable to connect to HTTPS proxy. Server response: ' . $response
       
   294             );
   242         }
   295         }
   243 
   296 
   244         // If all is good, switch socket to secure mode. We have to fall back
   297         // If all is good, switch socket to secure mode. We have to fall back
   245         // through the different modes
   298         // through the different modes
   246         $modes = array(
   299         $modes = array(
   251         );
   304         );
   252 
   305 
   253         $success = false;
   306         $success = false;
   254         foreach($modes as $mode) {
   307         foreach($modes as $mode) {
   255             $success = stream_socket_enable_crypto($this->socket, true, $mode);
   308             $success = stream_socket_enable_crypto($this->socket, true, $mode);
   256             if ($success) break;
   309             if ($success) {
   257         }
   310                 break;
   258 
   311             }
   259         if (! $success) {
   312         }
   260                 require_once 'Zend/Http/Client/Adapter/Exception.php';
   313 
   261                 throw new Zend_Http_Client_Adapter_Exception("Unable to connect to" .
   314         if (!$success) {
   262                     " HTTPS server through proxy: could not negotiate secure connection.");
   315             require_once 'Zend/Http/Client/Adapter/Exception.php';
       
   316             throw new Zend_Http_Client_Adapter_Exception(
       
   317                 'Unable to connect to HTTPS server through proxy: could not '
       
   318                 . 'negotiate secure connection.'
       
   319             );
   263         }
   320         }
   264     }
   321     }
   265 
   322 
   266     /**
   323     /**
   267      * Close the connection to the server
   324      * Close the connection to the server
   277      * Destructor: make sure the socket is disconnected
   334      * Destructor: make sure the socket is disconnected
   278      *
   335      *
   279      */
   336      */
   280     public function __destruct()
   337     public function __destruct()
   281     {
   338     {
   282         if ($this->socket) $this->close();
   339         if ($this->socket) {
       
   340             $this->close();
       
   341         }
   283     }
   342     }
   284 }
   343 }