diff --git a/src/HTTP/Requests/RemoteAddress.php b/src/HTTP/Requests/RemoteAddress.php new file mode 100644 index 0000000..1a05a62 --- /dev/null +++ b/src/HTTP/Requests/RemoteAddress.php @@ -0,0 +1,87 @@ +remoteAddress = $remoteAddress; + } + + public static function fromServer () : self { + return new self(self::extractRemoteAddressesFromHeaders()); + } + + /// + /// Format methods + /// + + public function has () : bool { + return $this->remoteAddress !== ""; + } + + public function getOne () : string { + if (strpos($this->remoteAddress, ',') === false) { + // We only have one value, it's the IP + return $this->remoteAddress; + } + + // Header contains 'clientIP, proxyIP, anotherProxyIP' + // The first value is so the one to return. + // See draft-ietf-appsawg-http-forwarded-10. + $ips = explode(',', $this->remoteAddress, 2); + return trim($ips[0]); + } + + public function getAll () : string { + return $this->remoteAddress; + } + + /// + /// Helper methods to determine the remote address + /// + + /** + * Allows to get all the remote addresses from relevant headers + */ + public static function extractRemoteAddressesFromHeaders () : string { + foreach (self::listRemoteAddressHeaders() as $candidate) { + if (isset($_SERVER[$candidate])) { + return $_SERVER[$candidate]; + } + } + + return ""; + } + + /// + /// Data sources + /// + + public static function listRemoteAddressHeaders () : array { + return [ + // Standard header provided by draft-ietf-appsawg-http-forwarded-10 + 'HTTP_X_FORWARDED_FOR', + + // Legacy headers + 'HTTP_CLIENT_IP', + 'HTTP_FORWARDED', + 'HTTP_FORWARDED_FOR', + 'HTTP_X_CLUSTER_CLIENT_IP', + 'HTTP_X_FORWARDED', + + // Default header if no proxy information could be detected + 'REMOTE_ADDR', + ]; + } +} diff --git a/src/HTTP/Requests/Request.php b/src/HTTP/Requests/Request.php new file mode 100644 index 0000000..2d8c2b3 --- /dev/null +++ b/src/HTTP/Requests/Request.php @@ -0,0 +1,10 @@ +getOne(); + } + +} diff --git a/tests/HTTP/Requests/RemoteAddressTest.php b/tests/HTTP/Requests/RemoteAddressTest.php new file mode 100644 index 0000000..7fb2491 --- /dev/null +++ b/tests/HTTP/Requests/RemoteAddressTest.php @@ -0,0 +1,72 @@ +assertEmpty($address->getOne()); + $this->assertEmpty($address->getAll()); + $this->assertFalse($address->has()); + } + + /** + * @covers \Keruald\OmniTools\HTTP\Requests\RemoteAddress::getOne + * @dataProvider provideTenZeroZeroThreeHeaderValues + */ + public function testGetOne (string $value) : void { + $address = new RemoteAddress($value); + + $this->assertEquals('10.0.0.3', $address->getOne()); + } + + /** + * @covers \Keruald\OmniTools\HTTP\Requests\RemoteAddress::getOne + * @dataProvider provideTenZeroZeroThreeHeaderValues + */ + public function testGetAll (string $value) : void { + $address = new RemoteAddress($value); + + $this->assertEquals($value, $address->getAll()); + } + + /** + * @covers \Keruald\OmniTools\HTTP\Requests\RemoteAddress::has + * @dataProvider provideTenZeroZeroThreeHeaderValues + */ + public function testHas (string $value) : void { + $address = new RemoteAddress($value); + + $this->assertTrue($address->has()); + } + + /// + /// Data provider + /// + + public function provideTenZeroZeroThreeHeaderValues () : iterable { + return [ + // Each value should return 10.0.0.3 + ['10.0.0.3'], + ['10.0.0.3,10.0.0.4'], + ['10.0.0.3, 10.0.0.4'], + ['10.0.0.3, 10.0.0.4, lorem ipsum dolor'], + ]; + } + +} diff --git a/tests/HTTP/Requests/RequestTest.php b/tests/HTTP/Requests/RequestTest.php new file mode 100644 index 0000000..6b721a1 --- /dev/null +++ b/tests/HTTP/Requests/RequestTest.php @@ -0,0 +1,33 @@ +assertEmpty(Request::getRemoteAddress()); + + $_SERVER = [ + 'REMOTE_ADDR' => '10.0.0.2', + ]; + $this->assertEquals('10.0.0.2', Request::getRemoteAddress()); + + $_SERVER += [ + 'HTTP_X_FORWARDED_FOR' => '10.0.0.3', + 'HTTP_CLIENT_IP' => '10.0.0.4', + ]; + $this->assertEquals( + '10.0.0.3', Request::getRemoteAddress(), + "HTTP_X_FORWARDED_FOR must be prioritized." + ); + } + +}