Index: library/Zend/Http/Client/Adapter/Proxy.php =================================================================== --- library/Zend/Http/Client/Adapter/Proxy.php (revision 24490) +++ library/Zend/Http/Client/Adapter/Proxy.php (working copy) @@ -75,6 +75,13 @@ * @var boolean */ protected $negotiated = false; + + /** + * Stores the last CONNECT handshake request + * + * @var string + */ + protected $connectHandshakeRequest; /** * Connect to the remote server @@ -136,10 +143,20 @@ } // Add Proxy-Authorization header - if ($this->config['proxy_user'] && ! isset($headers['proxy-authorization'])) { - $headers['proxy-authorization'] = Zend_Http_Client::encodeAuthHeader( - $this->config['proxy_user'], $this->config['proxy_pass'], $this->config['proxy_auth'] - ); + if ($this->config['proxy_user']) { + // Check to see if one already exists + $hasProxyAuthHeader = false; + foreach ($headers as $k => $v) { + if ($k == 'proxy-authorization' || preg_match("/^proxy-authorization:/i", $v) ) { + $hasProxyAuthHeader = true; + break; + } + } + if (!$hasProxyAuthHeader) { + $headers[] = 'Proxy-authorization: ' . Zend_Http_Client::encodeAuthHeader( + $this->config['proxy_user'], $this->config['proxy_pass'], $this->config['proxy_auth'] + ); + } } // if we are proxying HTTPS, preform CONNECT handshake with the proxy @@ -204,19 +221,22 @@ $request = "CONNECT $host:$port HTTP/$http_ver\r\n" . "Host: " . $this->config['proxy_host'] . "\r\n"; - // Add the user-agent header - if (isset($this->config['useragent'])) { - $request .= "User-agent: " . $this->config['useragent'] . "\r\n"; + // Process provided headers, including important ones to CONNECT request + foreach ( $headers as $k=>$v ) { + switch ( strtolower(substr($v,0,strpos($v,':'))) ) { + case 'proxy-authorization': + // break intentionally omitted + case 'user-agent': + $request .= $v . "\r\n"; + break; + default: + break; + } } - - // If the proxy-authorization header is set, send it to proxy but remove - // it from headers sent to target host - if (isset($headers['proxy-authorization'])) { - $request .= "Proxy-authorization: " . $headers['proxy-authorization'] . "\r\n"; - unset($headers['proxy-authorization']); - } - $request .= "\r\n"; + + // @see ZF-3189 + $this->connectHandshakeRequest = $request; // Send the request if (! @fwrite($this->socket, $request)) { Index: tests/Zend/Http/Client/ProxyAdapterTest.php =================================================================== --- tests/Zend/Http/Client/ProxyAdapterTest.php (revision 24490) +++ tests/Zend/Http/Client/ProxyAdapterTest.php (working copy) @@ -118,4 +118,98 @@ * the TRACE response */ } + + /** + * @group ZF-3189 + */ + public function testConnectHandshakeSendsCustomUserAgentHeader() + { + // Change the adapter + $this->config['adapter'] = 'ZF3189_ProxyAdapter'; + $this->config['useragent'] = 'ZendTest'; + parent::setUp(); + + $base = preg_replace("/^http:/", "https:", $this->baseuri); + $this->client->setUri($base . 'testSimpleRequests.php'); + + // Ensure we're proxying a HTTPS request + $this->assertEquals('https', $this->client->getUri()->getScheme()); + + // Perform the request + $this->client->request(); + + $this->assertRegExp( + "/\r\nUser-Agent: {$this->config['useragent']}\r\n/i", + $this->client->getAdapter()->getLastConnectHandshakeRequest() + ); + } + + /** + * @group ZF-3189 + */ + public function testConnectHandshakeSendsCustomUserAgentHeaderWhenSetInHeaders() + { + // Change the adapter + $this->config['adapter'] = 'ZF3189_ProxyAdapter'; + parent::setUp(); + + $base = preg_replace("/^http:/", "https:", $this->baseuri); + $this->client->setUri($base . 'testSimpleRequests.php'); + $this->client->setHeaders('User-Agent', 'ZendTest'); + + // Ensure we're proxying a HTTPS request + $this->assertEquals('https', $this->client->getUri()->getScheme()); + + // Perform the request + $this->client->request(); + print_r($this->client->getAdapter()->getLastConnectHandshakeRequest()); + $this->assertRegExp( + "/\r\nUser-Agent: ZendTest\r\n/i", + $this->client->getAdapter()->getLastConnectHandshakeRequest() + ); + } + + /** + * @group ZF-3189 + */ + public function testProxyAdapterDoesNotOverwriteExistingProxyAuthorizationHeader() + { + // Change the adapter + $this->config['adapter'] = 'ZF3189_ProxyAdapter'; + parent::setUp(); + + $base = preg_replace("/^http:/", "https:", $this->baseuri); + $this->client->setUri($base . 'testSimpleRequests.php'); + $this->client->setHeaders('Proxy-Authorization', 'FooBarBaz'); + + // Ensure we're proxying a HTTPS request + $this->assertEquals('https', $this->client->getUri()->getScheme()); + + // Perform the request + $this->client->request(); + print_r($this->client->getAdapter()->getLastConnectHandshakeRequest()); + + $resp = $this->client->getAdapter()->getLastConnectHandshakeRequest(); + $this->assertEquals(1, preg_match_all('/\r\nProxy-Authorization: ([^\r\n]+)\r\n/i', $resp, $matches)); + $this->assertEquals('FooBarBaz', $matches[1][0]); + } + } + +/** + * Exposes internal variable connectHandshakeRequest for test purposes + * @see ZF-3189 + */ +class ZF3189_ProxyAdapter extends Zend_Http_Client_Adapter_Proxy +{ + + /** + * Retrieve the request data from last CONNECT handshake + * @return string + */ + public function getLastConnectHandshakeRequest() + { + return $this->connectHandshakeRequest; + } + +} \ No newline at end of file