File indexing completed on 2025-01-19 05:21:14

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  * @see Zend_Http_Client
0030  */
0031 // require_once 'Zend/Http/Client.php';
0032 /**
0033  * @see Zend_Http_Client_Adapter_Socket
0034  */
0035 // require_once 'Zend/Http/Client/Adapter/Socket.php';
0036 
0037 /**
0038  * HTTP Proxy-supporting Zend_Http_Client adapter class, based on the default
0039  * socket based adapter.
0040  *
0041  * Should be used if proxy HTTP access is required. If no proxy is set, will
0042  * fall back to Zend_Http_Client_Adapter_Socket behavior. Just like the
0043  * default Socket adapter, this adapter does not require any special extensions
0044  * installed.
0045  *
0046  * @category   Zend
0047  * @package    Zend_Http
0048  * @subpackage Client_Adapter
0049  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0050  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0051  */
0052 class Zend_Http_Client_Adapter_Proxy extends Zend_Http_Client_Adapter_Socket
0053 {
0054     /**
0055      * Parameters array
0056      *
0057      * @var array
0058      */
0059     protected $config = array(
0060         'ssltransport'  => 'ssl',
0061         'sslcert'       => null,
0062         'sslpassphrase' => null,
0063         'sslusecontext' => false,
0064         'proxy_host'    => '',
0065         'proxy_port'    => 8080,
0066         'proxy_user'    => '',
0067         'proxy_pass'    => '',
0068         'proxy_auth'    => Zend_Http_Client::AUTH_BASIC,
0069         'persistent'    => false,
0070     );
0071 
0072     /**
0073      * Whether HTTPS CONNECT was already negotiated with the proxy or not
0074      *
0075      * @var boolean
0076      */
0077     protected $negotiated = false;
0078     
0079     /**
0080      * Stores the last CONNECT handshake request
0081      * 
0082      * @var string
0083      */
0084     protected $connectHandshakeRequest;
0085 
0086     /**
0087      * Connect to the remote server
0088      *
0089      * Will try to connect to the proxy server. If no proxy was set, will
0090      * fall back to the target server (behave like regular Socket adapter)
0091      *
0092      * @param string  $host
0093      * @param int     $port
0094      * @param boolean $secure
0095      */
0096     public function connect($host, $port = 80, $secure = false)
0097     {
0098         // If no proxy is set, fall back to Socket adapter
0099         if (!$this->config['proxy_host']) {
0100             return parent::connect($host, $port, $secure);
0101         }
0102 
0103         /* Url might require stream context even if proxy connection doesn't */
0104         if ($secure) {
0105             $this->config['sslusecontext'] = true;
0106         }
0107 
0108         // Connect (a non-secure connection) to the proxy server
0109         return parent::connect(
0110             $this->config['proxy_host'],
0111             $this->config['proxy_port'],
0112             false
0113         );
0114     }
0115 
0116     /**
0117      * Send request to the proxy server
0118      *
0119      * @param string        $method
0120      * @param Zend_Uri_Http $uri
0121      * @param string        $http_ver
0122      * @param array         $headers
0123      * @param string        $body
0124      * @return string Request as string
0125      * @throws Zend_Http_Client_Adapter_Exception
0126      */
0127     public function write(
0128         $method, $uri, $http_ver = '1.1', $headers = array(), $body = ''
0129     )
0130     {
0131         // If no proxy is set, fall back to default Socket adapter
0132         if (!$this->config['proxy_host']) {
0133             return parent::write($method, $uri, $http_ver, $headers, $body);
0134         }
0135 
0136         // Make sure we're properly connected
0137         if (!$this->socket) {
0138             // require_once 'Zend/Http/Client/Adapter/Exception.php';
0139             throw new Zend_Http_Client_Adapter_Exception(
0140                 'Trying to write but we are not connected'
0141             );
0142         }
0143 
0144         $host = $this->config['proxy_host'];
0145         $port = $this->config['proxy_port'];
0146 
0147         if ($this->connected_to[0] != "tcp://$host"
0148             || $this->connected_to[1] != $port
0149         ) {
0150             // require_once 'Zend/Http/Client/Adapter/Exception.php';
0151             throw new Zend_Http_Client_Adapter_Exception(
0152                 'Trying to write but we are connected to the wrong proxy server'
0153             );
0154         }
0155 
0156         // Add Proxy-Authorization header
0157         if ($this->config['proxy_user']) {
0158             // Check to see if one already exists
0159             $hasProxyAuthHeader = false;
0160             foreach ($headers as $k => $v) {
0161                 if ((string) $k == 'proxy-authorization'
0162                     || preg_match("/^proxy-authorization:/i", $v)
0163                 ) {
0164                     $hasProxyAuthHeader = true;
0165                     break;
0166                 }
0167             }
0168             if (!$hasProxyAuthHeader) {
0169                 $headers[] = 'Proxy-authorization: '
0170                     . Zend_Http_Client::encodeAuthHeader(
0171                         $this->config['proxy_user'],
0172                         $this->config['proxy_pass'], $this->config['proxy_auth']
0173                     );
0174             }
0175         }
0176 
0177         // if we are proxying HTTPS, preform CONNECT handshake with the proxy
0178         if ($uri->getScheme() == 'https' && (!$this->negotiated)) {
0179             $this->connectHandshake(
0180                 $uri->getHost(), $uri->getPort(), $http_ver, $headers
0181             );
0182             $this->negotiated = true;
0183         }
0184 
0185         // Save request method for later
0186         $this->method = $method;
0187 
0188         // Build request headers
0189         if ($this->negotiated) {
0190             $path = $uri->getPath();
0191             if ($uri->getQuery()) {
0192                 $path .= '?' . $uri->getQuery();
0193             }
0194             $request = "$method $path HTTP/$http_ver\r\n";
0195         } else {
0196             $request = "$method $uri HTTP/$http_ver\r\n";
0197         }
0198 
0199         // Add all headers to the request string
0200         foreach ($headers as $k => $v) {
0201             if (is_string($k)) $v = "$k: $v";
0202             $request .= "$v\r\n";
0203         }
0204 
0205         if(is_resource($body)) {
0206             $request .= "\r\n";
0207         } else {
0208             // Add the request body
0209             $request .= "\r\n" . $body;
0210         }
0211 
0212         // Send the request
0213         if (!@fwrite($this->socket, $request)) {
0214             // require_once 'Zend/Http/Client/Adapter/Exception.php';
0215             throw new Zend_Http_Client_Adapter_Exception(
0216                 'Error writing request to proxy server'
0217             );
0218         }
0219 
0220         if(is_resource($body)) {
0221             if(stream_copy_to_stream($body, $this->socket) == 0) {
0222                 // require_once 'Zend/Http/Client/Adapter/Exception.php';
0223                 throw new Zend_Http_Client_Adapter_Exception(
0224                     'Error writing request to server'
0225                 );
0226             }
0227         }
0228 
0229         return $request;
0230     }
0231 
0232     /**
0233      * Preform handshaking with HTTPS proxy using CONNECT method
0234      *
0235      * @param string  $host
0236      * @param integer $port
0237      * @param string  $http_ver
0238      * @param array   $headers
0239      * @return void
0240      * @throws Zend_Http_Client_Adapter_Exception
0241      */
0242     protected function connectHandshake(
0243         $host, $port = 443, $http_ver = '1.1', array &$headers = array()
0244     )
0245     {
0246         $request = "CONNECT $host:$port HTTP/$http_ver\r\n" .
0247                    "Host: " . $host . "\r\n";
0248 
0249         // Process provided headers, including important ones to CONNECT request
0250         foreach ($headers as $k => $v) {
0251             switch (strtolower(substr($v,0,strpos($v,':')))) {
0252                 case 'proxy-authorization':
0253                     // break intentionally omitted
0254 
0255                 case 'user-agent':
0256                     $request .= $v . "\r\n";
0257                     break;
0258 
0259                 default:
0260                     break;
0261             }
0262         }
0263         $request .= "\r\n";
0264         
0265         // @see ZF-3189
0266         $this->connectHandshakeRequest = $request;
0267 
0268         // Send the request
0269         if (!@fwrite($this->socket, $request)) {
0270             // require_once 'Zend/Http/Client/Adapter/Exception.php';
0271             throw new Zend_Http_Client_Adapter_Exception(
0272                 'Error writing request to proxy server'
0273             );
0274         }
0275 
0276         // Read response headers only
0277         $response = '';
0278         $gotStatus = false;
0279         while ($line = @fgets($this->socket)) {
0280             $gotStatus = $gotStatus || (strpos($line, 'HTTP') !== false);
0281             if ($gotStatus) {
0282                 $response .= $line;
0283                 if (!chop($line)) {
0284                     break;
0285                 }
0286             }
0287         }
0288 
0289         // Check that the response from the proxy is 200
0290         if (Zend_Http_Response::extractCode($response) != 200) {
0291             // require_once 'Zend/Http/Client/Adapter/Exception.php';
0292             throw new Zend_Http_Client_Adapter_Exception(
0293                 'Unable to connect to HTTPS proxy. Server response: ' . $response
0294             );
0295         }
0296 
0297         // If all is good, switch socket to secure mode. We have to fall back
0298         // through the different modes
0299         $modes = array(
0300             STREAM_CRYPTO_METHOD_TLS_CLIENT,
0301             STREAM_CRYPTO_METHOD_SSLv3_CLIENT,
0302             STREAM_CRYPTO_METHOD_SSLv23_CLIENT,
0303             STREAM_CRYPTO_METHOD_SSLv2_CLIENT
0304         );
0305 
0306         $success = false;
0307         foreach($modes as $mode) {
0308             $success = stream_socket_enable_crypto($this->socket, true, $mode);
0309             if ($success) {
0310                 break;
0311             }
0312         }
0313 
0314         if (!$success) {
0315             // require_once 'Zend/Http/Client/Adapter/Exception.php';
0316             throw new Zend_Http_Client_Adapter_Exception(
0317                 'Unable to connect to HTTPS server through proxy: could not '
0318                 . 'negotiate secure connection.'
0319             );
0320         }
0321     }
0322 
0323     /**
0324      * Close the connection to the server
0325      *
0326      */
0327     public function close()
0328     {
0329         parent::close();
0330         $this->negotiated = false;
0331     }
0332 
0333     /**
0334      * Destructor: make sure the socket is disconnected
0335      *
0336      */
0337     public function __destruct()
0338     {
0339         if ($this->socket) {
0340             $this->close();
0341         }
0342     }
0343 }