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 }