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.
18 * @subpackage Client_Adapter
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
27 require_once 'Zend/Uri/Http.php';
29 * @see Zend_Http_Client
31 require_once 'Zend/Http/Client.php';
33 * @see Zend_Http_Client_Adapter_Socket
35 require_once 'Zend/Http/Client/Adapter/Socket.php';
38 * HTTP Proxy-supporting Zend_Http_Client adapter class, based on the default
39 * socket based adapter.
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
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
52 class Zend_Http_Client_Adapter_Proxy extends Zend_Http_Client_Adapter_Socket
59 protected $config = array(
60 'ssltransport' => 'ssl',
62 'sslpassphrase' => null,
63 'sslusecontext' => false,
68 'proxy_auth' => Zend_Http_Client::AUTH_BASIC,
73 * Whether HTTPS CONNECT was already negotiated with the proxy or not
77 protected $negotiated = false;
80 * Connect to the remote server
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)
87 * @param boolean $secure
89 public function connect($host, $port = 80, $secure = false)
91 // If no proxy is set, fall back to Socket adapter
92 if (! $this->config['proxy_host']) {
93 return parent::connect($host, $port, $secure);
96 /* Url might require stream context even if proxy connection doesn't */
98 $this->config['sslusecontext'] = true;
101 // Connect (a non-secure connection) to the proxy server
102 return parent::connect(
103 $this->config['proxy_host'],
104 $this->config['proxy_port'],
110 * Send request to the proxy server
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
119 public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '')
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);
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");
130 $host = $this->config['proxy_host'];
131 $port = $this->config['proxy_port'];
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");
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']
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;
151 // Save request method for later
152 $this->method = $method;
154 // Build request headers
155 if ($this->negotiated) {
156 $path = $uri->getPath();
157 if ($uri->getQuery()) {
158 $path .= '?' . $uri->getQuery();
160 $request = "$method $path HTTP/$http_ver\r\n";
162 $request = "$method $uri HTTP/$http_ver\r\n";
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";
171 if(is_resource($body)) {
174 // Add the request body
175 $request .= "\r\n" . $body;
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");
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');
195 * Preform handshaking with HTTPS proxy using CONNECT method
197 * @param string $host
198 * @param integer $port
199 * @param string $http_ver
200 * @param array $headers
202 protected function connectHandshake($host, $port = 443, $http_ver = '1.1', array &$headers = array())
204 $request = "CONNECT $host:$port HTTP/$http_ver\r\n" .
205 "Host: " . $this->config['proxy_host'] . "\r\n";
207 // Add the user-agent header
208 if (isset($this->config['useragent'])) {
209 $request .= "User-agent: " . $this->config['useragent'] . "\r\n";
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']);
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");
227 // Read response headers only
230 while ($line = @fgets($this->socket)) {
231 $gotStatus = $gotStatus || (strpos($line, 'HTTP') !== false);
234 if (!chop($line)) break;
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);
244 // If all is good, switch socket to secure mode. We have to fall back
245 // through the different modes
247 STREAM_CRYPTO_METHOD_TLS_CLIENT,
248 STREAM_CRYPTO_METHOD_SSLv3_CLIENT,
249 STREAM_CRYPTO_METHOD_SSLv23_CLIENT,
250 STREAM_CRYPTO_METHOD_SSLv2_CLIENT
254 foreach($modes as $mode) {
255 $success = stream_socket_enable_crypto($this->socket, true, $mode);
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.");
267 * Close the connection to the server
270 public function close()
273 $this->negotiated = false;
277 * Destructor: make sure the socket is disconnected
280 public function __destruct()
282 if ($this->socket) $this->close();