assertEquals([], $request->parseAcceptHeader(' ')); $this->assertEquals([ 'audio/basic' => ['q' => 1], 'audio/*' => ['q' => 0.2], ], $request->parseAcceptHeader('audio/*; q=0.2, audio/basic')); $this->assertEquals([ 'application/json' => ['q' => 1, 'version' => '1.0'], 'application/xml' => ['q' => 1, 'version' => '2.0', 'x'], 'text/x-c' => ['q' => 1], 'text/x-dvi' => ['q' => 0.8], 'text/plain' => ['q' => 0.5], ], $request->parseAcceptHeader('text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0; x, text/x-dvi; q=0.8, text/x-c')); } public function testPreferredLanguage(): void { $this->mockApplication([ 'language' => 'en', ]); $request = new Request(); $request->acceptableLanguages = []; $this->assertEquals('en', $request->getPreferredLanguage()); $request = new Request(); $request->acceptableLanguages = ['de']; $this->assertEquals('en', $request->getPreferredLanguage()); $request = new Request(); $request->acceptableLanguages = ['en-us', 'de', 'ru-RU']; $this->assertEquals('en', $request->getPreferredLanguage(['en'])); $request = new Request(); $request->acceptableLanguages = ['en-us', 'de', 'ru-RU']; $this->assertEquals('de', $request->getPreferredLanguage(['ru', 'de'])); $this->assertEquals('de-DE', $request->getPreferredLanguage(['ru', 'de-DE'])); $request = new Request(); $request->acceptableLanguages = ['en-us', 'de', 'ru-RU']; $this->assertEquals('de', $request->getPreferredLanguage(['de', 'ru'])); $request = new Request(); $request->acceptableLanguages = ['en-us', 'de', 'ru-RU']; $this->assertEquals('ru-ru', $request->getPreferredLanguage(['ru-ru'])); $request = new Request(); $request->acceptableLanguages = ['en-us', 'de']; $this->assertEquals('ru-ru', $request->getPreferredLanguage(['ru-ru', 'pl'])); $this->assertEquals('ru-RU', $request->getPreferredLanguage(['ru-RU', 'pl'])); $request = new Request(); $request->acceptableLanguages = ['en-us', 'de']; $this->assertEquals('pl', $request->getPreferredLanguage(['pl', 'ru-ru'])); } /** * @see https://github.com/yiisoft/yii2/issues/14542 */ public function testCsrfTokenContainsASCIIOnly(): void { $this->mockWebApplication(); $request = new Request(); $request->enableCsrfCookie = false; $token = $request->getCsrfToken(); $this->assertMatchesRegularExpression('~[-_=a-z0-9]~i', $token); } public function testCsrfTokenValidation(): void { $this->mockWebApplication(); $request = new Request(); $request->enableCsrfCookie = false; $token = $request->getCsrfToken(); // accept any value if CSRF validation is disabled $request->enableCsrfValidation = false; $this->assertTrue($request->validateCsrfToken($token)); $this->assertTrue($request->validateCsrfToken($token . 'a')); $this->assertTrue($request->validateCsrfToken([])); $this->assertTrue($request->validateCsrfToken([$token])); $this->assertTrue($request->validateCsrfToken(0)); $this->assertTrue($request->validateCsrfToken(null)); // enable validation $request->enableCsrfValidation = true; // accept any value on GET request foreach (['GET', 'HEAD', 'OPTIONS'] as $method) { $_POST[$request->methodParam] = $method; $this->assertTrue($request->validateCsrfToken($token)); $this->assertTrue($request->validateCsrfToken($token . 'a')); $this->assertTrue($request->validateCsrfToken([])); $this->assertTrue($request->validateCsrfToken([$token])); $this->assertTrue($request->validateCsrfToken(0)); $this->assertTrue($request->validateCsrfToken(null)); } // only accept valid token on POST foreach (['POST', 'PUT', 'DELETE'] as $method) { $_POST[$request->methodParam] = $method; $this->assertTrue($request->validateCsrfToken($token)); $this->assertFalse($request->validateCsrfToken($token . 'a')); $this->assertFalse($request->validateCsrfToken([])); $this->assertFalse($request->validateCsrfToken([$token])); $this->assertFalse($request->validateCsrfToken(0)); $this->assertFalse($request->validateCsrfToken(null)); } } public function testIssue15317(): void { $this->mockWebApplication(); $_COOKIE[(new Request())->csrfParam] = ''; $request = new Request(); $request->enableCsrfCookie = true; $request->enableCookieValidation = false; $_SERVER['REQUEST_METHOD'] = 'POST'; \Yii::$app->security->unmaskToken(''); $this->assertFalse($request->validateCsrfToken('')); // When an empty CSRF token is given it is regenerated. $this->assertNotEmpty($request->getCsrfToken()); } /** * Test CSRF token validation by POST param. */ public function testCsrfTokenPost(): void { $this->mockWebApplication(); $request = new Request(); $request->enableCsrfCookie = false; $token = $request->getCsrfToken(); // accept no value on GET request foreach (['GET', 'HEAD', 'OPTIONS'] as $method) { $_POST[$request->methodParam] = $method; $this->assertTrue($request->validateCsrfToken()); } // only accept valid token on POST foreach (['POST', 'PUT', 'DELETE'] as $method) { $_POST[$request->methodParam] = $method; $request->setBodyParams([]); $this->assertFalse($request->validateCsrfToken()); $request->setBodyParams([$request->csrfParam => $token]); $this->assertTrue($request->validateCsrfToken()); } } /** * Test CSRF token validation by POST param. */ public function testCsrfTokenHeader(): void { $this->mockWebApplication(); $request = new Request(); $request->enableCsrfCookie = false; $token = $request->getCsrfToken(); // accept no value on GET request foreach (['GET', 'HEAD', 'OPTIONS'] as $method) { $_POST[$request->methodParam] = $method; $this->assertTrue($request->validateCsrfToken()); } // only accept valid token on POST foreach (['POST', 'PUT', 'DELETE'] as $method) { $_POST[$request->methodParam] = $method; $request->setBodyParams([]); $request->headers->remove(Request::CSRF_HEADER); $this->assertFalse($request->validateCsrfToken()); $request->headers->add(Request::CSRF_HEADER, $token); $this->assertTrue($request->validateCsrfToken()); } } public function testCustomSafeMethodsCsrfTokenValidation() { $this->mockWebApplication(); $request = new Request(); $request->csrfTokenSafeMethods = ['OPTIONS']; $request->enableCsrfCookie = false; $request->enableCsrfValidation = true; $token = $request->getCsrfToken(); // accept any value on custom safe request foreach (['OPTIONS'] as $method) { $_SERVER['REQUEST_METHOD'] = $method; $this->assertTrue($request->validateCsrfToken($token)); $this->assertTrue($request->validateCsrfToken($token . 'a')); $this->assertTrue($request->validateCsrfToken([])); $this->assertTrue($request->validateCsrfToken([$token])); $this->assertTrue($request->validateCsrfToken(0)); $this->assertTrue($request->validateCsrfToken(null)); $this->assertTrue($request->validateCsrfToken()); } // only accept valid token on other requests foreach (['GET', 'HEAD', 'POST'] as $method) { $_SERVER['REQUEST_METHOD'] = $method; $this->assertTrue($request->validateCsrfToken($token)); $this->assertFalse($request->validateCsrfToken($token . 'a')); $this->assertFalse($request->validateCsrfToken([])); $this->assertFalse($request->validateCsrfToken([$token])); $this->assertFalse($request->validateCsrfToken(0)); $this->assertFalse($request->validateCsrfToken(null)); $this->assertFalse($request->validateCsrfToken()); } } public function testCsrfHeaderValidation() { $this->mockWebApplication(); $request = new Request(); $request->validateCsrfHeaderOnly = true; $request->enableCsrfValidation = true; // only accept valid header on unsafe requests foreach (['GET', 'HEAD', 'POST'] as $method) { $_SERVER['REQUEST_METHOD'] = $method; $request->headers->remove(Request::CSRF_HEADER); $this->assertFalse($request->validateCsrfToken()); $request->headers->add(Request::CSRF_HEADER, ''); $this->assertTrue($request->validateCsrfToken()); } // accept no value on other requests foreach (['DELETE', 'PATCH', 'PUT', 'OPTIONS'] as $method) { $_SERVER['REQUEST_METHOD'] = $method; $this->assertTrue($request->validateCsrfToken()); } } public function testCustomHeaderCsrfHeaderValidation() { $this->mockWebApplication(); $request = new Request(); $request->csrfHeader = 'X-JGURDA'; $request->validateCsrfHeaderOnly = true; $request->enableCsrfValidation = true; // only accept valid header on unsafe requests foreach (['GET', 'HEAD', 'POST'] as $method) { $_SERVER['REQUEST_METHOD'] = $method; $request->headers->remove('X-JGURDA'); $this->assertFalse($request->validateCsrfToken()); $request->headers->add('X-JGURDA', ''); $this->assertTrue($request->validateCsrfToken()); } } public function testCustomUnsafeMethodsCsrfHeaderValidation() { $this->mockWebApplication(); $request = new Request(); $request->csrfHeaderUnsafeMethods = ['POST']; $request->validateCsrfHeaderOnly = true; $request->enableCsrfValidation = true; // only accept valid custom header on unsafe requests foreach (['POST'] as $method) { $_SERVER['REQUEST_METHOD'] = $method; $request->headers->remove(Request::CSRF_HEADER); $this->assertFalse($request->validateCsrfToken()); $request->headers->add(Request::CSRF_HEADER, ''); $this->assertTrue($request->validateCsrfToken()); } // accept no value on other requests foreach (['GET', 'HEAD'] as $method) { $_SERVER['REQUEST_METHOD'] = $method; $request->headers->remove(Request::CSRF_HEADER); $this->assertTrue($request->validateCsrfToken()); } } public function testNoCsrfTokenCsrfHeaderValidation() { $this->mockWebApplication(); $request = new Request(); $request->validateCsrfHeaderOnly = true; $this->assertEquals($request->getCsrfToken(), null); } public function testResolve() { $this->mockWebApplication([ 'components' => [ 'urlManager' => [ 'enablePrettyUrl' => true, 'showScriptName' => false, 'cache' => null, 'rules' => [ 'posts' => 'post/list', 'post/' => 'post/view', ], ], ], ]); $request = new Request(); $request->pathInfo = 'posts'; $_GET['page'] = 1; $result = $request->resolve(); $this->assertEquals(['post/list', ['page' => 1]], $result); $this->assertEquals($_GET, ['page' => 1]); $request->setQueryParams(['page' => 5]); $result = $request->resolve(); $this->assertEquals(['post/list', ['page' => 5]], $result); $this->assertEquals($_GET, ['page' => 1]); $request->setQueryParams(['custom-page' => 5]); $result = $request->resolve(); $this->assertEquals(['post/list', ['custom-page' => 5]], $result); $this->assertEquals($_GET, ['page' => 1]); unset($_GET['page']); $request = new Request(); $request->pathInfo = 'post/21'; $this->assertEquals($_GET, []); $result = $request->resolve(); $this->assertEquals(['post/view', ['id' => 21]], $result); $this->assertEquals($_GET, ['id' => 21]); $_GET['id'] = 42; $result = $request->resolve(); $this->assertEquals(['post/view', ['id' => 21]], $result); $this->assertEquals($_GET, ['id' => 21]); $_GET['id'] = 63; $request->setQueryParams(['token' => 'secret']); $result = $request->resolve(); $this->assertEquals(['post/view', ['id' => 21, 'token' => 'secret']], $result); $this->assertEquals($_GET, ['id' => 63]); } public static function getHostInfoDataProvider() { return [ // empty [ [], [null, null] ], // normal [ [ 'HTTP_HOST' => 'example1.com', 'SERVER_NAME' => 'example2.com', ], [ 'http://example1.com', 'example1.com', ] ], // HTTP header missing [ [ 'SERVER_NAME' => 'example2.com', ], [ 'http://example2.com', 'example2.com', ] ], // forwarded from untrusted server [ [ 'HTTP_X_FORWARDED_HOST' => 'example3.com', 'HTTP_HOST' => 'example1.com', 'SERVER_NAME' => 'example2.com', ], [ 'http://example1.com', 'example1.com', ] ], // forwarded from trusted proxy [ [ 'HTTP_X_FORWARDED_HOST' => 'example3.com', 'HTTP_HOST' => 'example1.com', 'SERVER_NAME' => 'example2.com', 'REMOTE_ADDR' => '192.168.0.1', ], [ 'http://example3.com', 'example3.com', ] ], // forwarded from trusted proxy [ [ 'HTTP_X_FORWARDED_HOST' => 'example3.com, example2.com', 'HTTP_HOST' => 'example1.com', 'SERVER_NAME' => 'example2.com', 'REMOTE_ADDR' => '192.168.0.1', ], [ 'http://example3.com', 'example3.com', ] ], // RFC 7239 forwarded from untrusted server [ [ 'HTTP_FORWARDED' => 'host=example3.com', 'HTTP_HOST' => 'example1.com', 'SERVER_NAME' => 'example2.com', ], [ 'http://example1.com', 'example1.com', ] ], // RFC 7239 forwarded from trusted proxy [ [ 'HTTP_FORWARDED' => 'host=example3.com', 'HTTP_HOST' => 'example1.com', 'REMOTE_ADDR' => '192.168.0.1', ], [ 'http://example3.com', 'example3.com', ] ], // RFC 7239 forwarded from trusted proxy [ [ 'HTTP_FORWARDED' => 'host=example3.com,host=example2.com', 'HTTP_HOST' => 'example1.com', 'REMOTE_ADDR' => '192.168.0.1', ], [ 'http://example2.com', 'example2.com', ] ], ]; } /** * @dataProvider getHostInfoDataProvider */ public function testGetHostInfo(array $server, array $expected): void { $original = $_SERVER; $_SERVER = $server; $request = new Request([ 'trustedHosts' => [ '192.168.0.0/24', ], 'secureHeaders' => [ 'X-Forwarded-For', 'X-Forwarded-Host', 'X-Forwarded-Proto', 'forwarded', ], ]); $this->assertEquals($expected[0], $request->getHostInfo()); $this->assertEquals($expected[1], $request->getHostName()); $request = new Request([ 'trustedHosts' => [ '192.168.0.0/24' => ['X-Forwarded-Host', 'forwarded'], ], 'secureHeaders' => [ 'X-Forwarded-For', 'X-Forwarded-Host', 'X-Forwarded-Proto', 'forwarded', ], ]); $this->assertEquals($expected[0], $request->getHostInfo()); $this->assertEquals($expected[1], $request->getHostName()); $_SERVER = $original; } public function testSetHostInfo(): void { $request = new Request(); unset($_SERVER['SERVER_NAME'], $_SERVER['HTTP_HOST']); $this->assertNull($request->getHostInfo()); $this->assertNull($request->getHostName()); $request->setHostInfo('http://servername.com:80'); $this->assertSame('http://servername.com:80', $request->getHostInfo()); $this->assertSame('servername.com', $request->getHostName()); } public function testGetScriptFileWithEmptyServer(): void { $request = new Request(); $_SERVER = []; $this->expectException(\yii\base\InvalidConfigException::class); $request->getScriptFile(); } public function testGetScriptUrlWithEmptyServer(): void { $request = new Request(); $_SERVER = []; $this->expectException(\yii\base\InvalidConfigException::class); $request->getScriptUrl(); } public function testGetServerName(): void { $request = new Request(); $_SERVER['SERVER_NAME'] = 'servername'; $this->assertEquals('servername', $request->getServerName()); unset($_SERVER['SERVER_NAME']); $this->assertNull($request->getServerName()); } public function testGetServerPort(): void { $request = new Request(); $_SERVER['SERVER_PORT'] = 33; $this->assertEquals(33, $request->getServerPort()); unset($_SERVER['SERVER_PORT']); $this->assertNull($request->getServerPort()); } public static function isSecureServerDataProvider() { return [ [['HTTPS' => 1], true], [['HTTPS' => 'on'], true], [['HTTPS' => 0], false], [['HTTPS' => 'off'], false], [[], false], [['HTTP_X_FORWARDED_PROTO' => 'https'], false], [['HTTP_X_FORWARDED_PROTO' => 'http'], false], [[ 'HTTP_X_FORWARDED_PROTO' => 'https', 'REMOTE_HOST' => 'test.com', ], false], [[ 'HTTP_X_FORWARDED_PROTO' => 'https', 'REMOTE_HOST' => 'othertest.com', ], false], [[ 'HTTP_X_FORWARDED_PROTO' => 'https', 'REMOTE_ADDR' => '192.168.0.1', ], true], [[ 'HTTP_X_FORWARDED_PROTO' => 'https', 'REMOTE_ADDR' => '192.169.0.1', ], false], [['HTTP_FRONT_END_HTTPS' => 'on'], false], [['HTTP_FRONT_END_HTTPS' => 'off'], false], [[ 'HTTP_FRONT_END_HTTPS' => 'on', 'REMOTE_HOST' => 'test.com', ], false], [[ 'HTTP_FRONT_END_HTTPS' => 'on', 'REMOTE_HOST' => 'othertest.com', ], false], [[ 'HTTP_FRONT_END_HTTPS' => 'on', 'REMOTE_ADDR' => '192.168.0.1', ], true], [[ 'HTTP_FRONT_END_HTTPS' => 'on', 'REMOTE_ADDR' => '192.169.0.1', ], false], // RFC 7239 forwarded from untrusted proxy [[ 'HTTP_FORWARDED' => 'proto=https', ], false], // RFC 7239 forwarded from two untrusted proxies [[ 'HTTP_FORWARDED' => 'proto=https,proto=http', ], false], // RFC 7239 forwarded from trusted proxy [[ 'HTTP_FORWARDED' => 'proto=https', 'REMOTE_ADDR' => '192.168.0.1', ], true], // RFC 7239 forwarded from trusted proxy, second proxy not encrypted [[ 'HTTP_FORWARDED' => 'proto=https,proto=http', 'REMOTE_ADDR' => '192.168.0.1', ], false], // RFC 7239 forwarded from trusted proxy, second proxy encrypted, while client request not encrypted [[ 'HTTP_FORWARDED' => 'proto=http,proto=https', 'REMOTE_ADDR' => '192.168.0.1', ], true], // RFC 7239 forwarded from untrusted proxy [[ 'HTTP_FORWARDED' => 'proto=https', 'REMOTE_ADDR' => '192.169.0.1', ], false], // RFC 7239 forwarded from untrusted proxy, second proxy not encrypted [[ 'HTTP_FORWARDED' => 'proto=https,proto=http', 'REMOTE_ADDR' => '192.169.0.1', ], false], // RFC 7239 forwarded from untrusted proxy, second proxy encrypted, while client request not encrypted [[ 'HTTP_FORWARDED' => 'proto=http,proto=https', 'REMOTE_ADDR' => '192.169.0.1', ], false], ]; } /** * @dataProvider isSecureServerDataProvider */ public function testGetIsSecureConnection(array $server, bool $expected): void { $original = $_SERVER; $_SERVER = $server; $request = new Request([ 'trustedHosts' => [ '192.168.0.0/24', ], 'secureHeaders' => [ 'Front-End-Https', 'X-Rewrite-Url', 'X-Forwarded-For', 'X-Forwarded-Host', 'X-Forwarded-Proto', 'forwarded', ], ]); $this->assertEquals($expected, $request->getIsSecureConnection()); $request = new Request([ 'trustedHosts' => [ '192.168.0.0/24' => ['Front-End-Https', 'X-Forwarded-Proto', 'forwarded'], ], 'secureHeaders' => [ 'Front-End-Https', 'X-Rewrite-Url', 'X-Forwarded-For', 'X-Forwarded-Host', 'X-Forwarded-Proto', 'forwarded', ], ]); $this->assertEquals($expected, $request->getIsSecureConnection()); $_SERVER = $original; } public static function isSecureServerWithoutTrustedHostDataProvider() { return [ // RFC 7239 forwarded header is not enabled [[ 'HTTP_FORWARDED' => 'proto=https', 'REMOTE_ADDR' => '192.168.0.1', ], false], ]; } /** * @dataProvider isSecureServerWithoutTrustedHostDataProvider */ public function testGetIsSecureConnectionWithoutTrustedHost(array $server, bool $expected): void { $original = $_SERVER; $_SERVER = $server; $request = new Request([ 'trustedHosts' => [ '192.168.0.0/24' => ['Front-End-Https', 'X-Forwarded-Proto'], ], 'secureHeaders' => [ 'Front-End-Https', 'X-Rewrite-Url', 'X-Forwarded-For', 'X-Forwarded-Host', 'X-Forwarded-Proto', 'forwarded', ], ]); $this->assertEquals($expected, $request->getIsSecureConnection()); $_SERVER = $original; } public static function getUserIPDataProvider() { return [ [ [ 'HTTP_X_FORWARDED_PROTO' => 'https', 'HTTP_X_FORWARDED_FOR' => '123.123.123.123', 'REMOTE_ADDR' => '192.168.0.1', ], '123.123.123.123', ], [ [ 'HTTP_X_FORWARDED_PROTO' => 'https', 'HTTP_X_FORWARDED_FOR' => '123.123.123.123', 'REMOTE_ADDR' => '192.169.1.1', ], '192.169.1.1', ], [ [ 'HTTP_X_FORWARDED_PROTO' => 'https', 'HTTP_X_FORWARDED_FOR' => '123.123.123.123', 'REMOTE_HOST' => 'untrusted.com', 'REMOTE_ADDR' => '192.169.1.1', ], '192.169.1.1', ], [ [ 'HTTP_X_FORWARDED_PROTO' => 'https', 'HTTP_X_FORWARDED_FOR' => '192.169.1.1', 'REMOTE_HOST' => 'untrusted.com', 'REMOTE_ADDR' => '192.169.1.1', ], '192.169.1.1', ], // RFC 7239 forwarded from trusted proxy [ [ 'HTTP_FORWARDED' => 'for=123.123.123.123', 'REMOTE_ADDR' => '192.168.0.1', ], '123.123.123.123', ], // RFC 7239 forwarded from trusted proxy with optinal port [ [ 'HTTP_FORWARDED' => 'for=123.123.123.123:2222', 'REMOTE_ADDR' => '192.168.0.1', ], '123.123.123.123', ], // RFC 7239 forwarded from trusted proxy, through another proxy [ [ 'HTTP_FORWARDED' => 'for=123.123.123.123,for=122.122.122.122', 'REMOTE_ADDR' => '192.168.0.1', ], '122.122.122.122', ], // RFC 7239 forwarded from trusted proxy, through another proxy, client IP with optional port [ [ 'HTTP_FORWARDED' => 'for=123.123.123.123:2222,for=122.122.122.122:2222', 'REMOTE_ADDR' => '192.168.0.1', ], '122.122.122.122', ], // RFC 7239 forwarded from untrusted proxy [ [ 'HTTP_FORWARDED' => 'for=123.123.123.123', 'REMOTE_ADDR' => '192.169.1.1', ], '192.169.1.1', ], // RFC 7239 forwarded from trusted proxy with optional port [ [ 'HTTP_FORWARDED' => 'for=123.123.123.123:2222', 'REMOTE_ADDR' => '192.169.1.1', ], '192.169.1.1', ], // RFC 7239 forwarded from trusted proxy with client IPv6 [ [ 'HTTP_FORWARDED' => 'for="2001:0db8:85a3:0000:0000:8a2e:0370:7334"', 'REMOTE_ADDR' => '192.168.0.1', ], '2001:0db8:85a3:0000:0000:8a2e:0370:7334', ], // RFC 7239 forwarded from trusted proxy with client IPv6 and optional port [ [ 'HTTP_FORWARDED' => 'for="[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:2222"', 'REMOTE_ADDR' => '192.168.0.1', ], '2001:0db8:85a3:0000:0000:8a2e:0370:7334', ], // RFC 7239 forwarded from trusted proxy, through another proxy with client IPv6 [ [ 'HTTP_FORWARDED' => 'for=122.122.122.122,for="2001:0db8:85a3:0000:0000:8a2e:0370:7334"', 'REMOTE_ADDR' => '192.168.0.1', ], '2001:0db8:85a3:0000:0000:8a2e:0370:7334', ], // RFC 7239 forwarded from trusted proxy, through another proxy with client IPv6 and optional port [ [ 'HTTP_FORWARDED' => 'for=122.122.122.122:2222,for="[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:2222"', 'REMOTE_ADDR' => '192.168.0.1', ], '2001:0db8:85a3:0000:0000:8a2e:0370:7334', ], // RFC 7239 forwarded from untrusted proxy with client IPv6 [ [ 'HTTP_FORWARDED' => 'for"=2001:0db8:85a3:0000:0000:8a2e:0370:7334"', 'REMOTE_ADDR' => '192.169.1.1', ], '192.169.1.1', ], // RFC 7239 forwarded from untrusted proxy, through another proxy with client IPv6 and optional port [ [ 'HTTP_FORWARDED' => 'for="[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:2222"', 'REMOTE_ADDR' => '192.169.1.1', ], '192.169.1.1', ], ]; } /** * @dataProvider getUserIPDataProvider */ public function testGetUserIP(array $server, string $expected): void { $original = $_SERVER; $_SERVER = $server; $request = new Request([ 'trustedHosts' => [ '192.168.0.0/24', ], 'secureHeaders' => [ 'Front-End-Https', 'X-Rewrite-Url', 'X-Forwarded-For', 'X-Forwarded-Host', 'X-Forwarded-Proto', 'forwarded', ], ]); $this->assertEquals($expected, $request->getUserIP()); $request = new Request([ 'trustedHosts' => [ '192.168.0.0/24' => ['X-Forwarded-For', 'forwarded'], ], 'secureHeaders' => [ 'Front-End-Https', 'X-Rewrite-Url', 'X-Forwarded-For', 'X-Forwarded-Host', 'X-Forwarded-Proto', 'forwarded', ], ]); $this->assertEquals($expected, $request->getUserIP()); $_SERVER = $original; } public static function getUserIPWithoutTruestHostDataProvider() { return [ // RFC 7239 forwarded is not enabled [ [ 'HTTP_FORWARDED' => 'for=123.123.123.123', 'REMOTE_ADDR' => '192.168.0.1', ], '192.168.0.1', ], ]; } /** * @dataProvider getUserIPWithoutTruestHostDataProvider */ public function testGetUserIPWithoutTrustedHost(array $server, string $expected): void { $original = $_SERVER; $_SERVER = $server; $request = new Request([ 'trustedHosts' => [ '192.168.0.0/24' => ['X-Forwarded-For'], ], 'secureHeaders' => [ 'Front-End-Https', 'X-Rewrite-Url', 'X-Forwarded-For', 'X-Forwarded-Host', 'X-Forwarded-Proto', 'forwarded', ], ]); $this->assertEquals($expected, $request->getUserIP()); $_SERVER = $original; } public static function getMethodDataProvider() { return [ [ [ 'REQUEST_METHOD' => 'DEFAULT', 'HTTP_X-HTTP-METHOD-OVERRIDE' => 'OVERRIDE', ], 'OVERRIDE', ], [ [ 'REQUEST_METHOD' => 'DEFAULT', ], 'DEFAULT', ], ]; } /** * @dataProvider getMethodDataProvider */ public function testGetMethod(array $server, string $expected): void { $original = $_SERVER; $_SERVER = $server; $request = new Request(); $this->assertEquals($expected, $request->getMethod()); $_SERVER = $original; } public static function getIsAjaxDataProvider() { return [ [ [ ], false, ], [ [ 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest', ], true, ], ]; } /** * @dataProvider getIsAjaxDataProvider */ public function testGetIsAjax(array $server, bool $expected): void { $original = $_SERVER; $_SERVER = $server; $request = new Request(); $this->assertEquals($expected, $request->getIsAjax()); $_SERVER = $original; } public static function getIsPjaxDataProvider() { return [ [ [ ], false, ], [ [ 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest', 'HTTP_X_PJAX' => 'any value', ], true, ], ]; } /** * @dataProvider getIsPjaxDataProvider */ public function testGetIsPjax(array $server, bool $expected): void { $original = $_SERVER; $_SERVER = $server; $request = new Request(); $this->assertEquals($expected, $request->getIsPjax()); $_SERVER = $original; } public function testGetOrigin(): void { $_SERVER['HTTP_ORIGIN'] = 'https://www.w3.org'; $request = new Request(); $this->assertEquals('https://www.w3.org', $request->getOrigin()); unset($_SERVER['HTTP_ORIGIN']); $request = new Request(); $this->assertNull($request->getOrigin()); } public static function httpAuthorizationHeadersProvider() { return [ ['not a base64 at all', [base64_decode('not a base64 at all'), null]], [base64_encode('user:'), ['user', null]], [base64_encode('user'), ['user', null]], [base64_encode('user:pw'), ['user', 'pw']], [base64_encode('user:pw'), ['user', 'pw']], [base64_encode('user:a:b'), ['user', 'a:b']], [base64_encode(':a:b'), [null, 'a:b']], [base64_encode(':'), [null, null]], ]; } /** * @dataProvider httpAuthorizationHeadersProvider */ public function testHttpAuthCredentialsFromHttpAuthorizationHeader(string $secret, array $expected): void { $original = $_SERVER; $request = new Request(); $_SERVER['HTTP_AUTHORIZATION'] = 'Basic ' . $secret; $this->assertSame($request->getAuthCredentials(), $expected); $this->assertSame($request->getAuthUser(), $expected[0]); $this->assertSame($request->getAuthPassword(), $expected[1]); $_SERVER = $original; $request = new Request(); $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] = 'Basic ' . $secret; $this->assertSame($request->getAuthCredentials(), $expected); $this->assertSame($request->getAuthUser(), $expected[0]); $this->assertSame($request->getAuthPassword(), $expected[1]); $_SERVER = $original; } public function testHttpAuthCredentialsFromServerSuperglobal(): void { $original = $_SERVER; [$user, $pw] = ['foo', 'bar']; $_SERVER['PHP_AUTH_USER'] = $user; $_SERVER['PHP_AUTH_PW'] = $pw; $request = new Request(); $request->getHeaders()->set('Authorization', 'Basic ' . base64_encode('less-priority:than-PHP_AUTH_*')); $this->assertSame($request->getAuthCredentials(), [$user, $pw]); $this->assertSame($request->getAuthUser(), $user); $this->assertSame($request->getAuthPassword(), $pw); $_SERVER = $original; } public function testGetBodyParam(): void { $request = new Request(); $request->setBodyParams([ 'someParam' => 'some value', 'param.dot' => 'value.dot', ]); $this->assertSame('some value', $request->getBodyParam('someParam')); $this->assertSame('value.dot', $request->getBodyParam('param.dot')); $this->assertSame(null, $request->getBodyParam('unexisting')); $this->assertSame('default', $request->getBodyParam('unexisting', 'default')); // @see https://github.com/yiisoft/yii2/issues/14135 $bodyParams = new \stdClass(); $bodyParams->someParam = 'some value'; $bodyParams->{'param.dot'} = 'value.dot'; $request->setBodyParams($bodyParams); $this->assertSame('some value', $request->getBodyParam('someParam')); $this->assertSame('value.dot', $request->getBodyParam('param.dot')); $this->assertSame(null, $request->getBodyParam('unexisting')); $this->assertSame('default', $request->getBodyParam('unexisting', 'default')); } public static function getBodyParamsDataProvider() { return [ 'json' => ['application/json', '{"foo":"bar","baz":1}', ['foo' => 'bar', 'baz' => 1]], 'jsonp' => ['application/javascript', 'parseResponse({"foo":"bar","baz":1});', ['foo' => 'bar', 'baz' => 1]], 'get' => ['application/x-www-form-urlencoded', 'foo=bar&baz=1', ['foo' => 'bar', 'baz' => '1']], ]; } /** * @dataProvider getBodyParamsDataProvider */ public function testGetBodyParams(string $contentType, string $rawBody, array $expected): void { $_SERVER['CONTENT_TYPE'] = $contentType; $request = new Request(); $request->parsers = [ 'application/json' => 'yii\web\JsonParser', 'application/javascript' => 'yii\web\JsonParser', ]; $request->setRawBody($rawBody); $this->assertSame($expected, $request->getBodyParams()); } public function trustedHostAndInjectedXForwardedForDataProvider() { return [ 'emptyIPs' => ['1.1.1.1', '', null, ['10.10.10.10'], '1.1.1.1'], 'invalidIp' => ['1.1.1.1', '127.0.0.1, 8.8.8.8, 2.2.2.2, apple', null, ['10.10.10.10'], '1.1.1.1'], 'invalidIp2' => ['1.1.1.1', '127.0.0.1, 8.8.8.8, 2.2.2.2, 300.300.300.300', null, ['10.10.10.10'], '1.1.1.1'], 'invalidIp3' => ['1.1.1.1', '127.0.0.1, 8.8.8.8, 2.2.2.2, 10.0.0.0/26', null, ['10.0.0.0/24'], '1.1.1.1'], 'invalidLatestIp' => ['1.1.1.1', '127.0.0.1, 8.8.8.8, 2.2.2.2, apple, 2.2.2.2', null, ['1.1.1.1', '2.2.2.2'], '2.2.2.2'], 'notTrusted' => ['1.1.1.1', '127.0.0.1, 8.8.8.8, 2.2.2.2', null, ['10.10.10.10'], '1.1.1.1'], 'trustedLevel1' => ['1.1.1.1', '127.0.0.1, 8.8.8.8, 2.2.2.2', null, ['1.1.1.1'], '2.2.2.2'], 'trustedLevel2' => ['1.1.1.1', '127.0.0.1, 8.8.8.8, 2.2.2.2', null, ['1.1.1.1', '2.2.2.2'], '8.8.8.8'], 'trustedLevel3' => ['1.1.1.1', '127.0.0.1, 8.8.8.8, 2.2.2.2', null, ['1.1.1.1', '2.2.2.2', '8.8.8.8'], '127.0.0.1'], 'trustedLevel4' => ['1.1.1.1', '127.0.0.1, 8.8.8.8, 2.2.2.2', null, ['1.1.1.1', '2.2.2.2', '8.8.8.8', '127.0.0.1'], '127.0.0.1'], 'trustedLevel4EmptyElements' => ['1.1.1.1', '127.0.0.1, 8.8.8.8,,,, , , 2.2.2.2', null, ['1.1.1.1', '2.2.2.2', '8.8.8.8', '127.0.0.1'], '127.0.0.1'], 'trustedWithCidr' => ['10.0.0.2', '127.0.0.1, 8.8.8.8, 10.0.0.240, 10.0.0.32, 10.0.0.99', null, ['10.0.0.0/24'], '8.8.8.8'], 'trustedAll' => ['10.0.0.2', '127.0.0.1, 8.8.8.8, 10.0.0.240, 10.0.0.32, 10.0.0.99', null, ['0.0.0.0/0'], '127.0.0.1'], 'emptyIpHeaders' => ['1.1.1.1', '127.0.0.1, 8.8.8.8, 2.2.2.2', [], ['1.1.1.1'], '1.1.1.1'], ]; } /** * @dataProvider trustedHostAndInjectedXForwardedForDataProvider */ public function testTrustedHostAndInjectedXForwardedFor(string $remoteAddress, string $xForwardedFor, ?array $ipHeaders, array $trustedHosts, string $expectedUserIp): void { $_SERVER['REMOTE_ADDR'] = $remoteAddress; $_SERVER['HTTP_X_FORWARDED_FOR'] = $xForwardedFor; $params = [ 'trustedHosts' => $trustedHosts, ]; if($ipHeaders !== null) { $params['ipHeaders'] = $ipHeaders; } $request = new Request($params); $this->assertSame($expectedUserIp, $request->getUserIP()); } public static function trustedHostAndXForwardedPortDataProvider() { return [ 'defaultPlain' => ['1.1.1.1', 80, null, null, 80], 'defaultSSL' => ['1.1.1.1', 443, null, null, 443], 'untrustedForwardedSSL' => ['1.1.1.1', 80, 443, ['10.0.0.0/8'], 80], 'untrustedForwardedPlain' => ['1.1.1.1', 443, 80, ['10.0.0.0/8'], 443], 'trustedForwardedSSL' => ['10.10.10.10', 80, 443, ['10.0.0.0/8'], 443], 'trustedForwardedPlain' => ['10.10.10.10', 443, 80, ['10.0.0.0/8'], 80], ]; } /** * @dataProvider trustedHostAndXForwardedPortDataProvider */ public function testTrustedHostAndXForwardedPort(string $remoteAddress, int $requestPort, ?int $xForwardedPort, ?array $trustedHosts, int $expectedPort): void { $_SERVER['REMOTE_ADDR'] = $remoteAddress; $_SERVER['SERVER_PORT'] = $requestPort; $_SERVER['HTTP_X_FORWARDED_PORT'] = $xForwardedPort; $params = [ 'trustedHosts' => $trustedHosts, ]; $request = new Request($params); $this->assertSame($expectedPort, $request->getServerPort()); } /** * @testWith ["POST", "GET", "POST"] * ["POST", "OPTIONS", "POST"] * ["POST", "HEAD", "POST"] * ["POST", "DELETE", "DELETE"] * ["POST", "CUSTOM", "CUSTOM"] */ public function testRequestMethodCanNotBeDowngraded($requestMethod, $requestOverrideMethod, $expectedMethod): void { $request = new Request(); $_SERVER['REQUEST_METHOD'] = $requestMethod; $_POST[$request->methodParam] = $requestOverrideMethod; $this->assertSame($expectedMethod, $request->getMethod()); } public static function alreadyResolvedIpDataProvider() { return [ 'resolvedXForwardedFor' => [ '50.0.0.1', '1.1.1.1, 8.8.8.8, 9.9.9.9', 'http', ['0.0.0.0/0'], // checks: '50.0.0.1', '50.0.0.1', false, ], 'resolvedXForwardedForWithHttps' => [ '50.0.0.1', '1.1.1.1, 8.8.8.8, 9.9.9.9', 'https', ['0.0.0.0/0'], // checks: '50.0.0.1', '50.0.0.1', true, ], ]; } /** * @dataProvider alreadyResolvedIpDataProvider */ public function testAlreadyResolvedIp($remoteAddress, $xForwardedFor, $xForwardedProto, $trustedHosts, $expectedRemoteAddress, $expectedUserIp, $expectedIsSecureConnection): void { $_SERVER['REMOTE_ADDR'] = $remoteAddress; $_SERVER['HTTP_X_FORWARDED_FOR'] = $xForwardedFor; $_SERVER['HTTP_X_FORWARDED_PROTO'] = $xForwardedProto; $request = new Request([ 'trustedHosts' => $trustedHosts, 'ipHeaders' => [] ]); $this->assertSame($expectedRemoteAddress, $request->remoteIP, 'Remote IP fail!'); $this->assertSame($expectedUserIp, $request->userIP, 'User IP fail!'); $this->assertSame($expectedIsSecureConnection, $request->isSecureConnection, 'Secure connection fail!'); } public static function parseForwardedHeaderDataProvider() { return [ [ '192.168.10.10', 'for=10.0.0.2;host=yiiframework.com;proto=https', 'https://yiiframework.com', '10.0.0.2' ], [ '192.168.10.10', 'for=10.0.0.2;proto=https', 'https://example.com', '10.0.0.2' ], [ '192.168.10.10', 'host=yiiframework.com;proto=https', 'https://yiiframework.com', '192.168.10.10' ], [ '192.168.10.10', 'host=yiiframework.com;for=10.0.0.2', 'http://yiiframework.com', '10.0.0.2' ], [ '192.168.20.10', 'host=yiiframework.com;for=10.0.0.2;proto=https', 'https://yiiframework.com', '10.0.0.2' ], [ '192.168.10.10', 'for=10.0.0.1;host=yiiframework.com;proto=https, for=192.168.20.20;host=awesome.proxy.com;proto=http', 'https://yiiframework.com', '10.0.0.1' ], [ '192.168.10.10', 'for=8.8.8.8;host=spoofed.host;proto=https, for=10.0.0.1;host=yiiframework.com;proto=https, for=192.168.20.20;host=trusted.proxy;proto=http', 'https://yiiframework.com', '10.0.0.1' ] ]; } /** * @dataProvider parseForwardedHeaderDataProvider */ public function testParseForwardedHeaderParts($remoteAddress, $forwardedHeader, $expectedHostInfo, $expectedUserIp): void { $_SERVER['REMOTE_ADDR'] = $remoteAddress; $_SERVER['HTTP_HOST'] = 'example.com'; $_SERVER['HTTP_FORWARDED'] = $forwardedHeader; $request = new Request([ 'trustedHosts' => [ '192.168.10.0/24', '192.168.20.0/24' ], 'secureHeaders' => [ 'X-Forwarded-For', 'X-Forwarded-Host', 'X-Forwarded-Proto', 'forwarded', ], ]); $this->assertSame($expectedUserIp, $request->userIP, 'User IP fail!'); $this->assertSame($expectedHostInfo, $request->hostInfo, 'Host info fail!'); } public function testForwardedNotTrusted(): void { $_SERVER['REMOTE_ADDR'] = '192.168.10.10'; $_SERVER['HTTP_HOST'] = 'example.com'; $_SERVER['HTTP_FORWARDED'] = 'for=8.8.8.8;host=spoofed.host;proto=https'; $_SERVER['HTTP_X_FORWARDED_FOR'] = '10.0.0.1'; $_SERVER['HTTP_X_FORWARDED_HOST'] = 'yiiframework.com'; $_SERVER['HTTP_X_FORWARDED_PROTO'] = 'http'; $request = new Request([ 'trustedHosts' => [ '192.168.10.0/24', '192.168.20.0/24' ], 'secureHeaders' => [ 'X-Forwarded-For', 'X-Forwarded-Host', 'X-Forwarded-Proto', ], ]); $this->assertSame('10.0.0.1', $request->userIP, 'User IP fail!'); $this->assertSame('http://yiiframework.com', $request->hostInfo, 'Host info fail!'); } }