Skip to content

Commit 6a7c380

Browse files
committed
Adds port detection to ProxyAwareSchemer
1 parent 98843ca commit 6a7c380

File tree

2 files changed

+90
-17
lines changed

2 files changed

+90
-17
lines changed

src/ProxyAwareSchemer.php

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,53 +32,97 @@ class ProxyAwareSchemer {
3232
'HTTPS' => 'on',
3333
];
3434

35+
public const PORT_EXPECTED_SERVER_KEYS = [
36+
'HTTP_X_FORWARDED_PORT',
37+
];
38+
39+
/** Value for `default port` arguments to remove the port from the URI */
40+
public const REMOVE_PORT = -65536;
41+
3542
/** @var array|string[] */
36-
private $proxyServerValues;
43+
private $proxyServerHttpsKeyValues;
44+
45+
/** @var string[] */
46+
private $proxyServerPortKeys;
3747

3848
/**
39-
* @param array|null $proxyServerValues Map of $_SERVER keys to their expected https-positive value. Defaults to
40-
* self::HTTPS_EXPECTED_SERVER_VALUES
41-
* @param array|null $server Server array to inspect. Defaults to $_SERVER.
49+
* @param array|null $server Server array to inspect. Defaults to $_SERVER.
50+
*
51+
* @param array|null $proxyServerHttpsKeyValues Map of $_SERVER keys to their expected https-positive value.
52+
* Defaults to ProxyAwareSchemer::HTTPS_EXPECTED_SERVER_VALUES
53+
*
54+
* @param string[]|null Array of $_SERVER keys to check for a forwarded port value.
4255
*/
4356
public function __construct(
44-
?array $proxyServerValues = null,
45-
?array $server = null
57+
?array $server = null,
58+
?array $proxyServerHttpsKeyValues = null,
59+
?array $proxyServerPortKeys = null
4660
) {
47-
$this->proxyServerValues = $proxyServerValues ?? self::HTTPS_EXPECTED_SERVER_VALUES;
48-
4961
if( is_array($server) ) {
5062
$this->server = $server;
5163
} else {
5264
$this->server = $_SERVER;
5365
}
66+
67+
$this->proxyServerHttpsKeyValues = $proxyServerHttpsKeyValues ?? self::HTTPS_EXPECTED_SERVER_VALUES;
68+
$this->proxyServerPortKeys = $proxyServerPortKeys ?? self::PORT_EXPECTED_SERVER_KEYS;
5469
}
5570

5671
/**
5772
* Given a \Psr\Http\Message\ServerRequestInterface returns a new instance of ServerRequestInterface with a new Uri
5873
* having the scheme adjusted to match the detected external scheme as defined by the proxies headers.
5974
*/
6075
public function withUriWithDetectedScheme(
61-
ServerRequestInterface $serverRequest
76+
ServerRequestInterface $serverRequest, bool $detectPort = true, ?int $defaultOnHttps = self::REMOVE_PORT
6277
) : ServerRequestInterface {
6378
return $serverRequest->withUri(
64-
$this->withDetectedScheme($serverRequest->getUri())
79+
$this->withDetectedScheme($serverRequest->getUri(), $detectPort, $defaultOnHttps)
6580
);
6681
}
6782

6883
/**
69-
* Given a \Psr\Http\Message\UriInterface returns a new instance of UriInterface having the scheme adjusted to match
84+
* Given a \Psr\Http\Message\UriInterface returns a instance of UriInterface having the scheme adjusted to match
7085
* the detected external scheme as defined by the proxies headers.
7186
*/
72-
public function withDetectedScheme( UriInterface $uri ) : UriInterface {
73-
foreach( $this->proxyServerValues as $serverKey => $serverValue ) {
87+
public function withDetectedScheme(
88+
UriInterface $uri,
89+
bool $detectPort = true,
90+
?int $defaultOnHttps = self::REMOVE_PORT
91+
) : UriInterface {
92+
foreach( $this->proxyServerHttpsKeyValues as $serverKey => $serverValue ) {
7493
if( isset($this->server[$serverKey])
7594
&& strtolower($this->server[$serverKey]) === $serverValue
7695
) {
77-
return $uri->withScheme('https');
96+
$newUri = $uri->withScheme('https');
97+
98+
return $detectPort ? $this->withDetectedPort($newUri, $defaultOnHttps) : $newUri;
7899
}
79100
}
80101

81-
return $uri;
102+
return $detectPort ? $this->withDetectedPort($uri) : $uri;
103+
}
104+
105+
/**
106+
* Given a \Psr\Http\Message\UriInterface returns a instance of UriInterface having the port adjusted to match
107+
* the detected external scheme as defined by the proxies headers.
108+
*
109+
* @param int|null $default Defines a default fallback port
110+
*/
111+
public function withDetectedPort( UriInterface $uri, ?int $default = null ) : UriInterface {
112+
foreach( $this->proxyServerPortKeys as $portKey ) {
113+
if( isset($this->server[$portKey]) ) {
114+
$port = (int)$this->server[$portKey];
115+
if( $port > 0 && $port <= 65535 ) {
116+
return $uri->withPort($port);
117+
}
118+
}
119+
}
120+
121+
if( $default === self::REMOVE_PORT ) {
122+
return $uri->withPort(null);
123+
}
124+
125+
return $default ? $uri->withPort($default) : $uri;
82126
}
83127

84128
}

test/ProxyAwareSchemerTest.php

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public function test_withDetectedScheme_https( string $key, string $value ) : vo
1515
$request = new ServerRequest('GET', 'http://localhost');
1616

1717
$server = [ $key => $value ];
18-
$schemerManuallyPassed = new ProxyAwareSchemer(null, $server);
18+
$schemerManuallyPassed = new ProxyAwareSchemer($server);
1919
$this->assertSame('https', $schemerManuallyPassed->withUriWithDetectedScheme($request)->getUri()->getScheme());
2020

2121
$_SERVER = [ $key => $value ];
@@ -28,7 +28,7 @@ public function test_withDetectedScheme_http() : void {
2828
$request = new ServerRequest('GET', 'http://localhost');
2929

3030
$server = [];
31-
$schemerManuallyPassed = new ProxyAwareSchemer(null, $server);
31+
$schemerManuallyPassed = new ProxyAwareSchemer($server);
3232
$this->assertSame('http', $schemerManuallyPassed->withUriWithDetectedScheme($request)->getUri()->getScheme());
3333

3434
$_SERVER = [];
@@ -43,4 +43,33 @@ public function positiveServerKeyProvider() : \Generator {
4343
}
4444
}
4545

46+
/**
47+
* @dataProvider linkServerPortProvider
48+
*/
49+
public function test_forwarded_port(
50+
string $expected,
51+
string $link,
52+
array $server,
53+
bool $detectPort = true,
54+
int $default = ProxyAwareSchemer::REMOVE_PORT
55+
) : void {
56+
$request = new ServerRequest('GET', $link);
57+
58+
$schemer = new ProxyAwareSchemer($server);
59+
60+
$this->assertSame($expected, $schemer->withUriWithDetectedScheme($request, $detectPort, $default)->getUri()->__toString());
61+
}
62+
63+
public function linkServerPortProvider() : \Generator {
64+
yield 'protocol should stay the same 1' => [ 'https://test.example.com:8080/foo', 'https://test.example.com/foo', [ 'HTTP_X_FORWARDED_PORT' => 8080 ] ];
65+
yield 'protocol should stay the same 2' => [ 'http://test.example.com:8080/foo', 'http://test.example.com/foo', [ 'HTTP_X_FORWARDED_PORT' => 8080 ] ];
66+
67+
yield 'protocol should change and new port should be respected' => [ 'https://test.example.com:8080/foo', 'http://test.example.com/foo', [ 'HTTP_X_FORWARDED_PORT' => 8080, 'HTTP_X_FORWARDED_PROTOCOL' => 'https' ] ];
68+
69+
yield 'change in protocol should remove port' => [ 'https://test.example.com/foo', 'http://test.example.com/foo', [ 'HTTP_X_FORWARDED_PROTOCOL' => 'https' ] ];
70+
71+
yield 'Default port should overwrite when not found' => [ 'https://test.example.com:9090/foo', 'http://test.example.com/foo', [ 'HTTP_X_FORWARDED_PROTOCOL' => 'https' ], true, 9090 ];
72+
yield 'Default port should not overwrite when detection disabled' => [ 'https://test.example.com:2020/foo', 'http://test.example.com:2020/foo', [ 'HTTP_X_FORWARDED_PORT' => 8080, 'HTTP_X_FORWARDED_PROTOCOL' => 'https' ], false ];
73+
}
74+
4675
}

0 commit comments

Comments
 (0)