123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507 |
- <?php
- use LeProxy\LeProxy\LeProxyServer;
- use Psr\Http\Message\ServerRequestInterface;
- use React\Http\Server;
- use React\Socket\Server as Socket;
- use React\EventLoop\Factory;
- use React\Socket\Connector;
- use RingCentral\Psr7\Response;
- use React\Socket\ConnectionInterface;
- use Clue\React\Block;
- use React\Promise\Stream;
- use RingCentral\Psr7;
- use LeProxy\LeProxy\ConnectorFactory;
- class FunctionalLeProxyServerTest extends PHPUnit_Framework_TestCase
- {
- private $loop;
- private $socketProxy;
- private $socketOrigin;
- private $proxy;
- private $headers = array();
- public function setUp()
- {
- $this->loop = Factory::create();
- $this->socketOrigin = new Socket(8082, $this->loop);
- $origin = new Server(function (ServerRequestInterface $request) {
- return new Response(
- 200,
- $this->headers + array(
- 'X-Powered-By' => '',
- 'Date' => '',
- ),
- Psr7\str($request)
- );
- });
- $origin->listen($this->socketOrigin);
- $proxy = new LeProxyServer($this->loop);
- $this->socketProxy = $proxy->listen('127.0.0.1:8084', false);
- $this->proxy = $this->socketProxy->getAddress();
- }
- //private function setConnector()
- public function tearDown()
- {
- $this->socketOrigin->close();
- $this->socketProxy->close();
- }
- public function testPlainGetReturnsUpstreamResponseHeaders()
- {
- // connect to proxy and send absolute target URI
- $connector = new Connector($this->loop);
- $promise = $connector->connect($this->proxy)->then(function (ConnectionInterface $conn) {
- $conn->write("GET http://127.0.0.1:8082/ HTTP/1.1\r\n\r\n");
- return Stream\buffer($conn);
- });
- $response = Block\await($promise, $this->loop, 0.1);
- $this->assertStringStartsWith("HTTP/1.1 200 OK\r\n", $response);
- $this->assertNotContains("Server:", $response);
- $this->assertNotContains("Date:", $response);
- $this->assertContains("\r\n\r\nGET / HTTP/1.1\r\n", $response);
- }
- public function testPlainGetReturnsUpstreamResponseHeadersCustom()
- {
- $this->headers = array(
- 'Server' => 'React',
- 'Date' => 'Tue, 27 Jun 2017 12:52:16 GMT',
- 'X-Powered-By' => 'React'
- );
- // connect to proxy and send absolute target URI
- $connector = new Connector($this->loop);
- $promise = $connector->connect($this->proxy)->then(function (ConnectionInterface $conn) {
- $conn->write("GET http://127.0.0.1:8082/ HTTP/1.1\r\n\r\n");
- return Stream\buffer($conn);
- });
- $response = Block\await($promise, $this->loop, 0.1);
- $this->assertStringStartsWith("HTTP/1.1 200 OK\r\n", $response);
- $this->assertContains("Server: React\r\n", $response);
- $this->assertContains("Date: Tue, 27 Jun 2017 12:52:16 GMT", $response);
- $this->assertContains("X-Powered-By: React\r\n", $response);
- $this->assertContains("\r\n\r\nGET / HTTP/1.1\r\n", $response);
- $this->assertNotContains("User-Agent:", $response);
- }
- public function testPlainGetWithExplicitUserAgent()
- {
- // connect to proxy and send absolute target URI
- $connector = new Connector($this->loop);
- $promise = $connector->connect($this->proxy)->then(function (ConnectionInterface $conn) {
- $conn->write("GET http://127.0.0.1:8082/ HTTP/1.1\r\nUser-Agent: custom\r\n\r\n");
- return Stream\buffer($conn);
- });
- $response = Block\await($promise, $this->loop, 0.1);
- $this->assertStringStartsWith("HTTP/1.1 200 OK\r\n", $response);
- $this->assertContains("\r\n\r\nGET / HTTP/1.1\r\n", $response);
- $this->assertContains("User-Agent: custom\r\n", $response);
- }
- public function testPlainGetInvalidUriReturns502WithProxyResponseHeaders()
- {
- // connect to proxy and send absolute target URI
- $connector = new Connector($this->loop);
- $promise = $connector->connect($this->proxy)->then(function (ConnectionInterface $conn) {
- $conn->write("GET http://127.0.0.1:2/ HTTP/1.1\r\n\r\n");
- return Stream\buffer($conn);
- });
- $response = Block\await($promise, $this->loop, 0.1);
- $this->assertStringStartsWith("HTTP/1.1 502 Bad Gateway\r\n", $response);
- $this->assertContains("Server: LeProxy\r\n", $response);
- $this->assertContains("\r\n\r\nUnable to request:", $response);
- }
- public function testPlainGetWithoutPathUsesRootPath()
- {
- // connect to proxy and send absolute target URI
- $connector = new Connector($this->loop);
- $promise = $connector->connect($this->proxy)->then(function (ConnectionInterface $conn) {
- $conn->write("GET http://127.0.0.1:8082 HTTP/1.1\r\n\r\n");
- return Stream\buffer($conn);
- });
- $response = Block\await($promise, $this->loop, 0.1);
- $this->assertStringStartsWith("HTTP/1.1 200 OK\r\n", $response);
- $this->assertNotContains("Server:", $response);
- $this->assertContains("\r\n\r\nGET / HTTP/1.1\r\n", $response);
- }
- public function testPlainGetOverUnixDomainSocketProxy()
- {
- // close TCP/IP socket and restart random Unix domain socket (UDS) path
- $this->socketProxy->close();
- $proxy = new LeProxyServer($this->loop);
- $path = tempnam(sys_get_temp_dir(), 'test');
- unlink($path);
- $this->socketProxy = $proxy->listen($path, false);
- $this->proxy = $this->socketProxy->getAddress();
- // connect to proxy and send absolute target URI
- $connector = new Connector($this->loop);
- $promise = $connector->connect($this->proxy)->then(function (ConnectionInterface $conn) use ($path) {
- unlink($path);
- $conn->write("GET http://127.0.0.1:8082/ HTTP/1.1\r\n\r\n");
- return Stream\buffer($conn);
- });
- $response = Block\await($promise, $this->loop, 0.1);
- $this->assertStringStartsWith("HTTP/1.1 200 OK\r\n", $response);
- $this->assertNotContains("Server:", $response);
- $this->assertNotContains("Date:", $response);
- $this->assertContains("\r\n\r\nGET / HTTP/1.1\r\n", $response);
- }
- public function testPlainOptions()
- {
- // connect to proxy and send absolute target URI
- $connector = new Connector($this->loop);
- $promise = $connector->connect($this->proxy)->then(function (ConnectionInterface $conn) {
- $conn->write("OPTIONS http://127.0.0.1:8082/ HTTP/1.1\r\n\r\n");
- return Stream\buffer($conn);
- });
- $response = Block\await($promise, $this->loop, 0.1);
- $this->assertStringStartsWith("HTTP/1.1 200 OK\r\n", $response);
- $this->assertNotContains("Server:", $response);
- $this->assertContains("\r\n\r\nOPTIONS / HTTP/1.1\r\n", $response);
- }
- public function testPlainOptionsWithoutPathUsesAsteriskForm()
- {
- // connect to proxy and send absolute target URI
- $connector = new Connector($this->loop);
- $promise = $connector->connect($this->proxy)->then(function (ConnectionInterface $conn) {
- $conn->write("OPTIONS http://127.0.0.1:8082 HTTP/1.1\r\n\r\n");
- return Stream\buffer($conn);
- });
- $response = Block\await($promise, $this->loop, 0.1);
- $this->assertStringStartsWith("HTTP/1.1 200 OK\r\n", $response);
- $this->assertNotContains("Server:", $response);
- $this->assertContains("\r\n\r\nOPTIONS * HTTP/1.1\r\n", $response);
- }
- public function testConnectGet()
- {
- // connect to proxy and send CONNECT requets and then normal request
- $connector = new Connector($this->loop);
- $promise = $connector->connect($this->proxy)->then(function (ConnectionInterface $conn) {
- $conn->write("CONNECT 127.0.0.1:8082 HTTP/1.1\r\n\r\n");
- $conn->once('data', function () use ($conn) {
- $conn->write("GET / HTTP/1.1\r\n\r\n");
- });
- return Stream\buffer($conn);
- });
- $response = Block\await($promise, $this->loop, 0.2);
- $this->assertStringStartsWith("HTTP/1.1 200 OK\r\n", $response);
- $this->assertContains("Server: LeProxy\r\n", $response);
- $this->assertContains("\r\n\r\nHTTP/1.1 200 OK\r\n", $response);
- $this->assertContains("\r\n\r\nGET / HTTP/1.1\r\n", $response);
- }
- public function testConnectInvalidUriReturns502()
- {
- // connect to proxy and send CONNECT request
- $connector = new Connector($this->loop);
- $promise = $connector->connect($this->proxy)->then(function (ConnectionInterface $conn) {
- $conn->write("CONNECT 127.0.0.1:2 HTTP/1.1\r\n\r\n");
- return Stream\buffer($conn);
- });
- $response = Block\await($promise, $this->loop, 0.1);
- $this->assertStringStartsWith("HTTP/1.1 502 Bad Gateway\r\n", $response);
- $this->assertContains("Server: LeProxy\r\n", $response);
- $this->assertContains("\r\n\r\nUnable to connect:", $response);
- }
- public function testConnectGetWithValidAuth()
- {
- $this->socketProxy->close();
- $proxy = new LeProxyServer($this->loop);
- $this->socketProxy = $proxy->listen('user:pass@127.0.0.1:8084', false);
- $this->proxy = $this->socketProxy->getAddress();
- // connect to proxy and send CONNECT requets and then normal request
- $connector = new Connector($this->loop);
- $promise = $connector->connect($this->proxy)->then(function (ConnectionInterface $conn) {
- $conn->write("CONNECT 127.0.0.1:8082 HTTP/1.1\r\nProxy-Authorization: Basic dXNlcjpwYXNz\r\n\r\n");
- $conn->once('data', function () use ($conn) {
- $conn->write("GET / HTTP/1.1\r\n\r\n");
- });
- return Stream\buffer($conn);
- });
- $response = Block\await($promise, $this->loop, 0.2);
- $this->assertStringStartsWith("HTTP/1.1 200 OK\r\n", $response);
- $this->assertContains("Server: LeProxy\r\n", $response);
- $this->assertContains("\r\n\r\nHTTP/1.1 200 OK\r\n", $response);
- $this->assertContains("\r\n\r\nGET / HTTP/1.1\r\n", $response);
- }
- public function testConnectGetWithInvalidAuthFails()
- {
- $this->socketProxy->close();
- $proxy = new LeProxyServer($this->loop);
- $this->socketProxy = $proxy->listen('user:pass@127.0.0.1:8084', false);
- $this->proxy = $this->socketProxy->getAddress();
- // connect to proxy and send CONNECT requets and then normal request
- $connector = new Connector($this->loop);
- $promise = $connector->connect($this->proxy)->then(function (ConnectionInterface $conn) {
- $conn->write("CONNECT 127.0.0.1:8082 HTTP/1.1\r\n\r\n");
- $conn->once('data', function () use ($conn) {
- $conn->write("GET / HTTP/1.1\r\n\r\n");
- });
- return Stream\buffer($conn);
- });
- $response = Block\await($promise, $this->loop, 0.2);
- $this->assertStringStartsWith("HTTP/1.1 407 Proxy Authentication Required\r\n", $response);
- $this->assertContains("Server: LeProxy\r\n", $response);
- }
- public function testSocksGet()
- {
- // connect to proxy and send CONNECT requets and then normal request
- $connector = new Connector($this->loop);
- $promise = $connector->connect($this->proxy)->then(function (ConnectionInterface $conn) {
- $conn->write("\x05\x01\x00" . "\x05\x01\x00\x03\x09" . "localhost" . "\x1F\x92");
- $conn->once('data', function () use ($conn) {
- $conn->once('data', function () use ($conn) {
- $conn->write("GET / HTTP/1.1\r\n\r\n");
- });
- });
- return Stream\buffer($conn);
- });
- $response = Block\await($promise, $this->loop, 0.2);
- $this->assertStringStartsWith("\x05\x00" . "\x05\x00\x00\x01\x00\x00\x00\x00\x00\x00", $response);
- $this->assertContains("HTTP/1.1 200 OK\r\n", $response);
- $this->assertContains("\r\n\r\nGET / HTTP/1.1\r\n", $response);
- }
- public function testSocksBlockedWillReturnRulesetError()
- {
- $base = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
- $base->expects($this->never())->method('connect');
- $blocker = ConnectorFactory::createBlockingConnector(array('*'), $base);
- $this->socketProxy->close();
- $proxy = new LeProxyServer($this->loop, $blocker);
- $this->socketProxy = $proxy->listen('127.0.0.1:8084', false);
- $this->proxy = $this->socketProxy->getAddress();
- // connect to proxy and send CONNECT requets and then normal request
- $connector = new Connector($this->loop);
- $promise = $connector->connect($this->proxy)->then(function (ConnectionInterface $conn) {
- $conn->write("\x05\x01\x00" . "\x05\x01\x00\x03\x09" . "localhost" . "\x1F\x92");
- return Stream\buffer($conn);
- });
- $response = Block\await($promise, $this->loop, 0.2);
- $this->assertEquals("\x05\x00" . "\x05\x02\x00\x01\x00\x00\x00\x00\x00\x00", $response);
- }
- public function testPacDirect()
- {
- // connect to proxy and send direct request
- $connector = new Connector($this->loop);
- $promise = $connector->connect($this->proxy)->then(function (ConnectionInterface $conn) {
- $conn->write("GET /pac HTTP/1.1\r\n\r\n");
- return Stream\buffer($conn);
- });
- $response = Block\await($promise, $this->loop, 0.1);
- $this->assertStringStartsWith("HTTP/1.1 200 OK\r\n", $response);
- $this->assertContains("Server: LeProxy\r\n", $response);
- $this->assertContains("PROXY", $response);
- }
- public function testPacInvalidMethod()
- {
- // connect to proxy and send direct request with non-GET method
- $connector = new Connector($this->loop);
- $promise = $connector->connect($this->proxy)->then(function (ConnectionInterface $conn) {
- $conn->write("POST /pac HTTP/1.1\r\n\r\n");
- return Stream\buffer($conn);
- });
- $response = Block\await($promise, $this->loop, 0.1);
- $this->assertStringStartsWith("HTTP/1.1 405 Method Not Allowed\r\n", $response);
- }
- public function testPacPlain()
- {
- // connect to proxy and send plain request
- $connector = new Connector($this->loop);
- $uri = str_replace('tcp:', 'http:', $this->proxy);
- $promise = $connector->connect($this->proxy)->then(function (ConnectionInterface $conn) use ($uri) {
- $conn->write("GET $uri/pac HTTP/1.1\r\n\r\n");
- return Stream\buffer($conn);
- });
- $response = Block\await($promise, $this->loop, 0.1);
- $this->assertStringStartsWith("HTTP/1.1 200 OK\r\n", $response);
- $this->assertContains("PROXY", $response);
- }
- public function testPacPlainToInvalidHostWillReturnError()
- {
- // connect to proxy and send plain request to other host
- $connector = new Connector($this->loop);
- $promise = $connector->connect($this->proxy)->then(function (ConnectionInterface $conn) {
- $conn->write("GET http://127.0.0.1:2/pac HTTP/1.1\r\n\r\n");
- return Stream\buffer($conn);
- });
- $response = Block\await($promise, $this->loop, 0.1);
- $this->assertStringStartsWith("HTTP/1.1 502 Bad Gateway\r\n", $response);
- $this->assertContains("\r\n\r\nUnable to request:", $response);
- }
- public function testPacConnect()
- {
- // connect to proxy and send CONNECT request
- $connector = new Connector($this->loop);
- $uri = str_replace('tcp://', '', $this->proxy);
- $promise = $connector->connect($this->proxy)->then(function (ConnectionInterface $conn) use ($uri) {
- $conn->write("CONNECT $uri HTTP/1.1\r\n\r\n");
- $conn->once('data', function () use ($conn) {
- $conn->write("GET /pac HTTP/1.1\r\n\r\n");
- });
- return Stream\buffer($conn);
- });
- $response = Block\await($promise, $this->loop, 0.2);
- $this->assertStringStartsWith("HTTP/1.1 200 OK\r\n", $response);
- $this->assertContains("\r\n\r\nHTTP/1.1 200 OK\r\n", $response);
- $this->assertContains("PROXY", $response);
- }
- public function testDirectInvalid()
- {
- // connect to proxy and send direct request
- $connector = new Connector($this->loop);
- $promise = $connector->connect($this->proxy)->then(function (ConnectionInterface $conn) {
- $conn->write("GET /pac HTTP\r\n\r\n");
- return Stream\buffer($conn);
- });
- $response = Block\await($promise, $this->loop, 0.1);
- $this->assertStringStartsWith("HTTP/1.1 400 Bad Request\r\n", $response);
- //$this->assertContains("Server: LeProxy\r\n", $response);
- //$this->assertNotContains('X-Powered-By', $response);
- }
- public function testPlainPostWithChunkedTransferEncodingReturns411LengthRequired()
- {
- // connect to proxy and send request with (rare but valid) "Transfer-Encoding: chunked"
- $connector = new Connector($this->loop);
- $promise = $connector->connect($this->proxy)->then(function (ConnectionInterface $conn) {
- $conn->write("POST http://127.0.0.1:8082/ HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n");
- return Stream\buffer($conn);
- });
- $response = Block\await($promise, $this->loop, 0.1);
- $this->assertStringStartsWith("HTTP/1.1 411 Length Required\r\n", $response);
- $this->assertContains("Server: LeProxy", $response);
- $this->assertContains("\r\n\r\nLeProxy HTTP/SOCKS proxy does not allow buffering chunked requests", $response);
- }
- public function testPlainPostWithUnknownTransferEncodingReturns501NotImplemented()
- {
- // connect to proxy and send request with unknown "Transfer-Encoding: foo"
- $connector = new Connector($this->loop);
- $promise = $connector->connect($this->proxy)->then(function (ConnectionInterface $conn) {
- $conn->write("POST http://127.0.0.1:8082/ HTTP/1.1\r\nTransfer-Encoding: foo\r\n\r\n");
- return Stream\buffer($conn);
- });
- $response = Block\await($promise, $this->loop, 0.1);
- $this->assertStringStartsWith("HTTP/1.1 501 Not Implemented\r\n", $response);
- $this->assertNotContains("Server: LeProxy", $response);
- }
- public function testPlainPostWithChunkedTransferEncodingAndContentLengthReturns400BadRequest()
- {
- // connect to proxy and send invalid request with both "Transfer-Encoding: chunked" and "Content-Length"
- $connector = new Connector($this->loop);
- $promise = $connector->connect($this->proxy)->then(function (ConnectionInterface $conn) {
- $conn->write("POST http://127.0.0.1:8082/ HTTP/1.1\r\nTransfer-Encoding: chunked\r\nContent-Length: 0\r\n\r\n");
- return Stream\buffer($conn);
- });
- $response = Block\await($promise, $this->loop, 0.1);
- $this->assertStringStartsWith("HTTP/1.1 400 Bad Request\r\n", $response);
- $this->assertNotContains("Server: LeProxy", $response);
- }
- }
|