Page MenuHomeDevCentral

D1623.id4145.diff
No OneTemporary

D1623.id4145.diff

diff --git a/src/HTTP/Requests/RemoteAddress.php b/src/HTTP/Requests/RemoteAddress.php
new file mode 100644
--- /dev/null
+++ b/src/HTTP/Requests/RemoteAddress.php
@@ -0,0 +1,87 @@
+<?php
+declare(strict_types=1);
+
+namespace Keruald\OmniTools\HTTP\Requests;
+
+class RemoteAddress {
+
+ /**
+ * @var string
+ */
+ private $remoteAddress;
+
+ ///
+ /// Constructor
+ ///
+
+ public function __construct (string $remoteAddress = '') {
+ $this->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
--- /dev/null
+++ b/src/HTTP/Requests/Request.php
@@ -0,0 +1,10 @@
+<?php
+declare(strict_types=1);
+
+namespace Keruald\OmniTools\HTTP\Requests;
+
+class Request {
+
+ use WithRemoteAddress;
+
+}
diff --git a/src/HTTP/Requests/WithRemoteAddress.php b/src/HTTP/Requests/WithRemoteAddress.php
new file mode 100644
--- /dev/null
+++ b/src/HTTP/Requests/WithRemoteAddress.php
@@ -0,0 +1,25 @@
+<?php
+declare(strict_types=1);
+
+namespace Keruald\OmniTools\HTTP\Requests;
+
+trait WithRemoteAddress {
+
+ /**
+ * Gets remote IP address.
+ *
+ * This is intended as a drop-in replacement for $_SERVER['REMOTE_ADDR'],
+ * which takes in consideration proxy values, blindly trusted.
+ *
+ * This method should is only for environment where headers are controlled,
+ * like nginx + php_fpm, where HTTP_ headers are reserved for the server
+ * information, and where the headers sent by the web server to nginx are
+ * checked or populated by nginx itself.
+ *
+ * @return string the remote address
+ */
+ public static function getRemoteAddress () : string {
+ return RemoteAddress::fromServer()->getOne();
+ }
+
+}
diff --git a/tests/HTTP/Requests/RemoteAddressTest.php b/tests/HTTP/Requests/RemoteAddressTest.php
new file mode 100644
--- /dev/null
+++ b/tests/HTTP/Requests/RemoteAddressTest.php
@@ -0,0 +1,72 @@
+<?php
+declare(strict_types=1);
+
+namespace Keruald\OmniTools\Tests\HTTP\Requests;
+
+use Keruald\OmniTools\HTTP\Requests\RemoteAddress;
+use PHPUnit\Framework\TestCase;
+
+class RemoteAddressTest extends TestCase {
+
+ ///
+ /// Tests
+ ///
+
+ /**
+ * @covers \Keruald\OmniTools\HTTP\Requests\RemoteAddress::getOne
+ * @covers \Keruald\OmniTools\HTTP\Requests\RemoteAddress::getAll
+ * @covers \Keruald\OmniTools\HTTP\Requests\RemoteAddress::has
+ */
+ public function testEmptyRequest () : void {
+ $address = new RemoteAddress;
+
+ $this->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
--- /dev/null
+++ b/tests/HTTP/Requests/RequestTest.php
@@ -0,0 +1,33 @@
+<?php
+declare(strict_types=1);
+
+namespace Keruald\OmniTools\Tests\HTTP\Requests;
+
+use Keruald\OmniTools\HTTP\Requests\Request;
+use PHPUnit\Framework\TestCase;
+
+class RequestTest extends TestCase {
+
+ /**
+ * @covers \Keruald\OmniTools\HTTP\Requests\Request::getRemoteAddress
+ * @backupGlobals enabled
+ */
+ public function testGetRemoteAddress () : void {
+ $this->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."
+ );
+ }
+
+}

File Metadata

Mime Type
text/plain
Expires
Sun, Nov 24, 16:10 (2 h, 9 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2260504
Default Alt Text
D1623.id4145.diff (6 KB)

Event Timeline