assertTrue(RequestUtil::isSecure(['HTTPS' => 'on'])); $this->assertTrue(RequestUtil::isSecure(['HTTPS' => '1'])); $this->assertTrue(RequestUtil::isSecure(['HTTPS' => 'https'])); $this->assertTrue(RequestUtil::isSecure(['HTTPS' => 'ON'])); } public function testExplicitHttpsOffIsInsecure(): void { $this->assertFalse(RequestUtil::isSecure(['HTTPS' => 'off'])); $this->assertFalse(RequestUtil::isSecure(['HTTPS' => 'OFF'])); $this->assertFalse(RequestUtil::isSecure(['HTTPS' => ''])); } public function testPlainHttpWithLoopbackPeerIsInsecure(): void { // No HTTPS var, no proxy header, port 80 → plain HTTP. $this->assertFalse(RequestUtil::isSecure([ 'REMOTE_ADDR' => '127.0.0.1', 'SERVER_PORT' => '80', ])); } public function testForwardedProtoHttpsFromTrustedProxyIsSecure(): void { // The production topology: nginx terminates TLS and forwards to // PHP-FPM on loopback, setting X-Forwarded-Proto. $this->assertTrue(RequestUtil::isSecure([ 'REMOTE_ADDR' => '127.0.0.1', 'HTTP_X_FORWARDED_PROTO' => 'https', ])); } public function testForwardedProtoHttpsFromPrivateProxyIsSecure(): void { $this->assertTrue(RequestUtil::isSecure([ 'REMOTE_ADDR' => '10.0.0.5', 'HTTP_X_FORWARDED_PROTO' => 'https', ])); $this->assertTrue(RequestUtil::isSecure([ 'REMOTE_ADDR' => '192.168.1.10', 'HTTP_X_FORWARDED_PROTO' => 'https', ])); $this->assertTrue(RequestUtil::isSecure([ 'REMOTE_ADDR' => '::1', 'HTTP_X_FORWARDED_PROTO' => 'https', ])); } public function testForwardedProtoIgnoredFromUntrustedPeer(): void { // A remote client sending the header over plain HTTP must NOT be // treated as secure — otherwise the header is trivially spoofable. $this->assertFalse(RequestUtil::isSecure([ 'REMOTE_ADDR' => '203.0.113.42', 'HTTP_X_FORWARDED_PROTO' => 'https', ])); } public function testForwardedProtoListUsesFirstValue(): void { $this->assertTrue(RequestUtil::isSecure([ 'REMOTE_ADDR' => '127.0.0.1', 'HTTP_X_FORWARDED_PROTO' => 'https, http', ])); $this->assertFalse(RequestUtil::isSecure([ 'REMOTE_ADDR' => '127.0.0.1', 'HTTP_X_FORWARDED_PROTO' => 'http, https', ])); } public function testForwardedProtoHttpValueIsInsecure(): void { $this->assertFalse(RequestUtil::isSecure([ 'REMOTE_ADDR' => '127.0.0.1', 'HTTP_X_FORWARDED_PROTO' => 'http', ])); } public function testServerPortFallback(): void { $this->assertTrue(RequestUtil::isSecure(['SERVER_PORT' => 443])); $this->assertTrue(RequestUtil::isSecure(['SERVER_PORT' => '443'])); $this->assertFalse(RequestUtil::isSecure(['SERVER_PORT' => 8080])); } public function testDirectTlsBeatsUntrustedProxyHeader(): void { // HTTPS=on is authoritative even if a bogus proxy header says http. $this->assertTrue(RequestUtil::isSecure([ 'HTTPS' => 'on', 'REMOTE_ADDR' => '203.0.113.42', 'HTTP_X_FORWARDED_PROTO' => 'http', ])); } public function testBaseUrlRespectsProxyTermination(): void { $this->assertSame('https://xamxam.erg.be', RequestUtil::baseUrl([ 'REMOTE_ADDR' => '127.0.0.1', 'HTTP_X_FORWARDED_PROTO' => 'https', 'HTTP_HOST' => 'xamxam.erg.be', ])); } public function testBaseUrlFallsBackToLocalhost(): void { $this->assertSame('http://localhost', RequestUtil::baseUrl([])); } public function testIsTrustedProxyClassification(): void { $this->assertTrue(RequestUtil::isTrustedProxy(['REMOTE_ADDR' => '127.0.0.1'])); $this->assertTrue(RequestUtil::isTrustedProxy(['REMOTE_ADDR' => '::1'])); $this->assertTrue(RequestUtil::isTrustedProxy(['REMOTE_ADDR' => '172.16.0.1'])); $this->assertTrue(RequestUtil::isTrustedProxy(['REMOTE_ADDR' => 'fd00::1'])); $this->assertFalse(RequestUtil::isTrustedProxy(['REMOTE_ADDR' => '8.8.8.8'])); $this->assertFalse(RequestUtil::isTrustedProxy(['REMOTE_ADDR' => ''])); $this->assertFalse(RequestUtil::isTrustedProxy([])); } }