Skip to content

Commit 5414f98

Browse files
committed
Preserve all components from URI when passing through
1 parent 89ae4f6 commit 5414f98

8 files changed

+140
-13
lines changed

src/DnsConnector.php

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,58 @@ public function __construct(ConnectorInterface $connector, Resolver $resolver)
2020

2121
public function connect($uri)
2222
{
23-
$that = $this;
23+
if (strpos($uri, '://') === false) {
24+
$parts = parse_url('tcp://' . $uri);
25+
unset($parts['scheme']);
26+
} else {
27+
$parts = parse_url($uri);
28+
}
2429

25-
$parts = parse_url('tcp://' . $uri);
26-
if (!$parts || !isset($parts['host'], $parts['port'])) {
30+
if (!$parts || !isset($parts['host'])) {
2731
return Promise\reject(new \InvalidArgumentException('Given URI "' . $uri . '" is invalid'));
2832
}
2933

34+
$that = $this;
3035
$host = trim($parts['host'], '[]');
3136

3237
return $this
3338
->resolveHostname($host)
3439
->then(function ($ip) use ($that, $parts) {
40+
$uri = '';
41+
42+
// prepend original scheme if known
43+
if (isset($parts['scheme'])) {
44+
$uri .= $parts['scheme'] . '://';
45+
}
46+
3547
if (strpos($ip, ':') !== false) {
3648
// enclose IPv6 addresses in square brackets before appending port
37-
$ip = '[' . $ip . ']';
49+
$uri .= '[' . $ip . ']';
50+
} else {
51+
$uri .= $ip;
52+
}
53+
54+
// append original port if known
55+
if (isset($parts['port'])) {
56+
$uri .= ':' . $parts['port'];
57+
}
58+
59+
// append orignal path if known
60+
if (isset($parts['path'])) {
61+
$uri .= $parts['path'];
62+
}
63+
64+
// append original query if known
65+
if (isset($parts['query'])) {
66+
$uri .= '?' . $parts['query'];
67+
}
68+
69+
// append original fragment if known
70+
if (isset($parts['fragment'])) {
71+
$uri .= '#' . $parts['fragment'];
3872
}
3973

40-
return $that->connectTcp(
41-
$ip . ':' . $parts['port']
42-
);
74+
return $that->connectTcp($uri);
4375
});
4476
}
4577

src/SecureConnector.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,17 @@ public function connect($uri)
2626
return Promise\reject(new \BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)'));
2727
}
2828

29-
$host = trim(parse_url('tcp://' . $uri, PHP_URL_HOST), '[]');
29+
if (strpos($uri, '://') === false) {
30+
$uri = 'tls://' . $uri;
31+
}
32+
33+
$parts = parse_url($uri);
34+
if (!$parts || !isset($parts['host']) || $parts['scheme'] !== 'tls') {
35+
return Promise\reject(new \InvalidArgumentException('Given URI "' . $uri . '" is invalid'));
36+
}
37+
38+
$uri = str_replace('tls://', '', $uri);
39+
$host = trim($parts['host'], '[]');
3040

3141
$context = $this->context + array(
3242
'SNI_enabled' => true,

src/TcpConnector.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,16 @@ public function __construct(LoopInterface $loop, array $context = array())
2121

2222
public function connect($uri)
2323
{
24-
$parts = parse_url('tcp://' . $uri);
25-
if (!$parts || !isset($parts['host'], $parts['port'])) {
24+
if (strpos($uri, '://') === false) {
25+
$uri = 'tcp://' . $uri;
26+
}
27+
28+
$parts = parse_url($uri);
29+
if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
2630
return Promise\reject(new \InvalidArgumentException('Given URI "' . $uri . '" is invalid'));
2731
}
28-
$ip = trim($parts['host'], '[]');
2932

33+
$ip = trim($parts['host'], '[]');
3034
if (false === filter_var($ip, FILTER_VALIDATE_IP)) {
3135
return Promise\reject(new \InvalidArgumentException('Given URI "' . $ip . '" does not contain a valid host IP'));
3236
}

src/UnixConnector.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,13 @@ public function __construct(LoopInterface $loop)
2525

2626
public function connect($path)
2727
{
28-
$resource = @stream_socket_client('unix://' . $path, $errno, $errstr, 1.0);
28+
if (strpos($path, '://') === false) {
29+
$path = 'unix://' . $path;
30+
} elseif (substr($path, 0, 7) !== 'unix://') {
31+
return Promise\reject(new \InvalidArgumentException('Given URI "' . $path . '" is invalid'));
32+
}
33+
34+
$resource = @stream_socket_client($path, $errno, $errstr, 1.0);
2935

3036
if (!$resource) {
3137
return Promise\reject(new RuntimeException('Unable to connect to unix domain socket "' . $path . '": ' . $errstr, $errno));

tests/DnsConnectorTest.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,32 @@ public function testPassThroughResolverIfGivenHost()
3535
$this->connector->connect('google.com:80');
3636
}
3737

38+
public function testPassThroughResolverIfGivenHostWhichResolvesToIpv6()
39+
{
40+
$this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('::1')));
41+
$this->tcp->expects($this->once())->method('connect')->with($this->equalTo('[::1]:80'))->will($this->returnValue(Promise\reject()));
42+
43+
$this->connector->connect('google.com:80');
44+
}
45+
46+
public function testPassByResolverIfGivenCompleteUri()
47+
{
48+
$this->resolver->expects($this->never())->method('resolve');
49+
$this->tcp->expects($this->once())->method('connect')->with($this->equalTo('scheme://127.0.0.1:80/path?query#fragment'))->will($this->returnValue(Promise\reject()));
50+
51+
$this->connector->connect('scheme://127.0.0.1:80/path?query#fragment');
52+
}
53+
54+
public function testRejectsImmediatelyIfUriIsInvalid()
55+
{
56+
$this->resolver->expects($this->never())->method('resolve');
57+
$this->tcp->expects($this->never())->method('connect');
58+
59+
$promise = $this->connector->connect('////');
60+
61+
$promise->then($this->expectCallableNever(), $this->expectCallableOnce());
62+
}
63+
3864
public function testSkipConnectionIfDnsFails()
3965
{
4066
$this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.invalid'))->will($this->returnValue(Promise\reject()));

tests/SecureConnectorTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,23 @@ public function testConnectionWillWaitForTcpConnection()
3232
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
3333
}
3434

35+
public function testConnectionWithCompleteUriWillBePassedThroughExpectForScheme()
36+
{
37+
$pending = new Promise\Promise(function () { });
38+
$this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80/path?query#fragment'))->will($this->returnValue($pending));
39+
40+
$this->connector->connect('tls://example.com:80/path?query#fragment');
41+
}
42+
43+
public function testConnectionToInvalidSchemeWillReject()
44+
{
45+
$this->tcp->expects($this->never())->method('connect');
46+
47+
$promise = $this->connector->connect('tcp://example.com:80');
48+
49+
$promise->then(null, $this->expectCallableOnce());
50+
}
51+
3552
public function testCancelDuringTcpConnectionCancelsTcpConnection()
3653
{
3754
$pending = new Promise\Promise(function () { }, $this->expectCallableOnce());

tests/TcpConnectorTest.php

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public function connectionToHostnameShouldFailImmediately()
8989
}
9090

9191
/** @test */
92-
public function connectionToInvalidAddressShouldFailImmediately()
92+
public function connectionToInvalidPortShouldFailImmediately()
9393
{
9494
$loop = $this->getMock('React\EventLoop\LoopInterface');
9595

@@ -100,6 +100,32 @@ public function connectionToInvalidAddressShouldFailImmediately()
100100
);
101101
}
102102

103+
/** @test */
104+
public function connectionToInvalidSchemeShouldFailImmediately()
105+
{
106+
$loop = $this->getMock('React\EventLoop\LoopInterface');
107+
108+
$connector = new TcpConnector($loop);
109+
$connector->connect('tls://google.com:443')->then(
110+
$this->expectCallableNever(),
111+
$this->expectCallableOnce()
112+
);
113+
}
114+
115+
/** @test */
116+
public function connectionWithInvalidContextShouldFailImmediately()
117+
{
118+
$this->markTestIncomplete();
119+
120+
$loop = $this->getMock('React\EventLoop\LoopInterface');
121+
122+
$connector = new TcpConnector($loop, array('bindto' => 'invalid.invalid:123456'));
123+
$connector->connect('127.0.0.1:80')->then(
124+
$this->expectCallableNever(),
125+
$this->expectCallableOnce()
126+
);
127+
}
128+
103129
/** @test */
104130
public function cancellingConnectionShouldRejectPromise()
105131
{

tests/UnixConnectorTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ public function testInvalid()
2121
$promise->then(null, $this->expectCallableOnce());
2222
}
2323

24+
public function testInvalidScheme()
25+
{
26+
$promise = $this->connector->connect('tcp://google.com:80');
27+
$promise->then(null, $this->expectCallableOnce());
28+
}
29+
2430
public function testValid()
2531
{
2632
// random unix domain socket path

0 commit comments

Comments
 (0)