From 7258a76f492357874d0af1fe1f50a08209f61174 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 28 Aug 2017 17:49:52 +0200 Subject: [PATCH] Use EACCES error code if proxy uses 407 (Proxy Authentication Required) --- README.md | 3 ++- src/ProxyConnector.php | 11 +++++++---- tests/ProxyConnectorTest.php | 17 +++++++++++++++++ 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b7b89dd..20f8a5f 100644 --- a/README.md +++ b/README.md @@ -295,7 +295,8 @@ $proxy = new ProxyConnector( connection attempt. If the authentication details are missing or not accepted by the remote HTTP proxy server, it is expected to reject each connection attempt with a - `407` (Proxy Authentication Required) response status code. + `407` (Proxy Authentication Required) response status code and an exception + error code of `SOCKET_EACCES` (13). #### Advanced secure proxy connections diff --git a/src/ProxyConnector.php b/src/ProxyConnector.php index f4e07f3..c7801f8 100644 --- a/src/ProxyConnector.php +++ b/src/ProxyConnector.php @@ -151,11 +151,14 @@ public function connect($uri) return; } - // status must be 2xx - if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) { + if ($response->getStatusCode() === 407) { + // map status code 407 (Proxy Authentication Required) to EACCES + $deferred->reject(new RuntimeException('Proxy denied connection due to invalid authentication ' . $response->getStatusCode() . ' (' . $response->getReasonPhrase() . ') (EACCES)', defined('SOCKET_EACCES') ? SOCKET_EACCES : 13)); + return $stream->close(); + } elseif ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) { + // map non-2xx status code to ECONNREFUSED $deferred->reject(new RuntimeException('Proxy refused connection with HTTP error code ' . $response->getStatusCode() . ' (' . $response->getReasonPhrase() . ') (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111)); - $stream->close(); - return; + return $stream->close(); } // all okay, resolve with stream instance diff --git a/tests/ProxyConnectorTest.php b/tests/ProxyConnectorTest.php index a86508b..6b11828 100644 --- a/tests/ProxyConnectorTest.php +++ b/tests/ProxyConnectorTest.php @@ -208,6 +208,23 @@ public function testRejectsAndClosesIfStreamWritesTooMuchData() $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EMSGSIZE)); } + public function testRejectsAndClosesIfStreamReturnsProyAuthenticationRequired() + { + $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock(); + + $promise = \React\Promise\resolve($stream); + $this->connector->expects($this->once())->method('connect')->willReturn($promise); + + $proxy = new ProxyConnector('proxy.example.com', $this->connector); + + $promise = $proxy->connect('google.com:80'); + + $stream->expects($this->once())->method('close'); + $stream->emit('data', array("HTTP/1.1 407 Proxy Authentication Required\r\n\r\n")); + + $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EACCES)); + } + public function testRejectsAndClosesIfStreamReturnsNonSuccess() { $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();