Files
yii2/tests/framework/web/RequestTest.php
2025-04-26 19:03:07 -04:00

1395 lines
48 KiB
PHP

<?php
/**
* @link https://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license https://www.yiiframework.com/license/
*/
namespace yiiunit\framework\web;
use yii\web\Request;
use yiiunit\TestCase;
/**
* @group web
* @backupGlobals enabled
*/
class RequestTest extends TestCase
{
public function testParseAcceptHeader(): void
{
$request = new Request();
$this->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/<id>' => '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!');
}
}