Skip to content

Commit 11f9b10

Browse files
authored
Merge pull request #13 from clue-labs/invalid-uris
Fix rejecting invalid URIs and unexpected URI schemes
2 parents a0f60cc + ef22bcf commit 11f9b10

File tree

4 files changed

+59
-15
lines changed

4 files changed

+59
-15
lines changed

README.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ to the proxy server address:
101101

102102
```php
103103
$connector = new Connector($loop);
104-
$proxy = new ProxyConnector('127.0.0.1:8080', $connector);
104+
$proxy = new ProxyConnector('http://127.0.0.1:8080', $connector);
105105
```
106106

107107
The proxy URL may or may not contain a scheme and port definition. The default
@@ -119,7 +119,7 @@ higher-level component:
119119

120120
```diff
121121
- $client = new SomeClient($connector);
122-
+ $proxy = new ProxyConnector('127.0.0.1:8080', $connector);
122+
+ $proxy = new ProxyConnector('http://127.0.0.1:8080', $connector);
123123
+ $client = new SomeClient($proxy);
124124
```
125125

@@ -133,7 +133,7 @@ The `ProxyConnector` implements the [`ConnectorInterface`](#connectorinterface)
133133
hence provides a single public method, the [`connect()`](#connect) method.
134134

135135
```php
136-
$proxy = new ProxyConnector('127.0.0.1:8080', $connector);
136+
$proxy = new ProxyConnector('http://127.0.0.1:8080', $connector);
137137

138138
$proxy->connect('tcp://smtp.googlemail.com:587')->then(function (ConnectionInterface $stream) {
139139
$stream->write("EHLO local\r\n");
@@ -171,7 +171,7 @@ your destination, you may want to wrap this connector in React's
171171
[`SecureConnector`](https://github.com/reactphp/socket#secureconnector):
172172

173173
```php
174-
$proxy = new ProxyConnector('127.0.0.1:8080', $connector);
174+
$proxy = new ProxyConnector('http://127.0.0.1:8080', $connector);
175175
$connector = new Connector($loop, array(
176176
'tcp' => $proxy,
177177
'dns' => false
@@ -274,16 +274,17 @@ unencrypted, plain TCP/IP HTTP connection. Note that this is the most common
274274
setup, because you can still establish a TLS connection between you and the
275275
destination host as above.
276276

277-
If you want to connect to a (rather rare) HTTPS proxy, you may want use its
278-
HTTPS port (443) and use a
277+
If you want to connect to a (rather rare) HTTPS proxy, you may want use the
278+
`https://` scheme (HTTPS default port 443) and use React's
279+
[`Connector`](https://github.com/reactphp/socket#connector) or the low-level
279280
[`SecureConnector`](https://github.com/reactphp/socket#secureconnector)
280281
instance to create a secure connection to the proxy:
281282

282283
```php
283-
$ssl = new SecureConnector($connector, $loop);
284-
$proxy = new ProxyConnector('127.0.0.1:443', $ssl);
284+
$connector = new Connector($loop);
285+
$proxy = new ProxyConnector('https://127.0.0.1:443', $connector);
285286

286-
$proxy->connect('smtp.googlemail.com:587');
287+
$proxy->connect('tcp://smtp.googlemail.com:587');
287288
```
288289

289290
## Install

src/ProxyConnector.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use InvalidArgumentException;
88
use RuntimeException;
99
use RingCentral\Psr7;
10+
use React\Promise;
1011
use React\Promise\Deferred;
1112
use React\Socket\ConnectionInterface;
1213

@@ -60,16 +61,18 @@ public function __construct($proxyUrl, ConnectorInterface $connector)
6061
}
6162

6263
$parts = parse_url($proxyUrl);
63-
if (!$parts || !isset($parts['scheme'], $parts['host'])) {
64+
if (!$parts || !isset($parts['scheme'], $parts['host']) || ($parts['scheme'] !== 'http' && $parts['scheme'] !== 'https')) {
6465
throw new InvalidArgumentException('Invalid proxy URL');
6566
}
6667

68+
// apply default port and TCP/TLS transport for given scheme
6769
if (!isset($parts['port'])) {
6870
$parts['port'] = $parts['scheme'] === 'https' ? 443 : 80;
6971
}
72+
$parts['scheme'] = $parts['scheme'] === 'https' ? 'tls' : 'tcp';
7073

7174
$this->connector = $connector;
72-
$this->proxyUri = $parts['host'] . ':' . $parts['port'];
75+
$this->proxyUri = $parts['scheme'] . '://' . $parts['host'] . ':' . $parts['port'];
7376
}
7477

7578
public function connect($uri)

tests/FunctionalTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public function testSecureGoogleDoesNotAcceptConnectMethod()
4444
}
4545

4646
$secure = new SecureConnector($this->dnsConnector, $this->loop);
47-
$proxy = new ProxyConnector('google.com:443', $secure);
47+
$proxy = new ProxyConnector('https://google.com:443', $secure);
4848

4949
$promise = $proxy->connect('google.com:80');
5050

tests/ProxyConnectorTest.php

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,38 @@ public function testInvalidProxy()
2323
new ProxyConnector('///', $this->connector);
2424
}
2525

26+
/**
27+
* @expectedException InvalidArgumentException
28+
*/
29+
public function testInvalidProxyScheme()
30+
{
31+
new ProxyConnector('ftp://example.com', $this->connector);
32+
}
33+
2634
public function testCreatesConnectionToHttpPort()
2735
{
2836
$promise = new Promise(function () { });
29-
$this->connector->expects($this->once())->method('connect')->with('proxy.example.com:80?hostname=google.com')->willReturn($promise);
37+
$this->connector->expects($this->once())->method('connect')->with('tcp://proxy.example.com:80?hostname=google.com')->willReturn($promise);
3038

3139
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
3240

3341
$proxy->connect('google.com:80');
3442
}
3543

44+
public function testCreatesConnectionToHttpPortAndPassesThroughUriComponents()
45+
{
46+
$promise = new Promise(function () { });
47+
$this->connector->expects($this->once())->method('connect')->with('tcp://proxy.example.com:80/path?foo=bar&hostname=google.com#segment')->willReturn($promise);
48+
49+
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
50+
51+
$proxy->connect('google.com:80/path?foo=bar#segment');
52+
}
53+
3654
public function testCreatesConnectionToHttpPortAndObeysExplicitHostname()
3755
{
3856
$promise = new Promise(function () { });
39-
$this->connector->expects($this->once())->method('connect')->with('proxy.example.com:80?hostname=www.google.com')->willReturn($promise);
57+
$this->connector->expects($this->once())->method('connect')->with('tcp://proxy.example.com:80?hostname=www.google.com')->willReturn($promise);
4058

4159
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
4260

@@ -46,7 +64,7 @@ public function testCreatesConnectionToHttpPortAndObeysExplicitHostname()
4664
public function testCreatesConnectionToHttpsPort()
4765
{
4866
$promise = new Promise(function () { });
49-
$this->connector->expects($this->once())->method('connect')->with('proxy.example.com:443?hostname=google.com')->willReturn($promise);
67+
$this->connector->expects($this->once())->method('connect')->with('tls://proxy.example.com:443?hostname=google.com')->willReturn($promise);
5068

5169
$proxy = new ProxyConnector('https://proxy.example.com', $this->connector);
5270

@@ -80,6 +98,28 @@ public function testWillWriteToOpenConnection()
8098
$proxy->connect('google.com:80');
8199
}
82100

101+
public function testRejectsInvalidUri()
102+
{
103+
$this->connector->expects($this->never())->method('connect');
104+
105+
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
106+
107+
$promise = $proxy->connect('///');
108+
109+
$promise->then(null, $this->expectCallableOnce());
110+
}
111+
112+
public function testRejectsUriWithNonTcpScheme()
113+
{
114+
$this->connector->expects($this->never())->method('connect');
115+
116+
$proxy = new ProxyConnector('proxy.example.com', $this->connector);
117+
118+
$promise = $proxy->connect('tls://google.com:80');
119+
120+
$promise->then(null, $this->expectCallableOnce());
121+
}
122+
83123
public function testRejectsAndClosesIfStreamWritesNonHttp()
84124
{
85125
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();

0 commit comments

Comments
 (0)