CodeIgniter4/tests/system/CommonFunctionsTest.php

617 lines
17 KiB
PHP
Raw Normal View History

<?php
2016-07-12 10:11:17 -07:00
2021-07-19 21:32:33 +08:00
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter;
use CodeIgniter\Config\BaseService;
use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\HTTP\Response;
use CodeIgniter\HTTP\URI;
use CodeIgniter\HTTP\UserAgent;
use CodeIgniter\Router\RouteCollection;
use CodeIgniter\Session\Handlers\FileHandler;
2021-02-28 08:28:54 +07:00
use CodeIgniter\Session\Session;
use CodeIgniter\Test\CIUnitTestCase;
2022-02-05 13:55:03 +09:00
use CodeIgniter\Test\Mock\MockCodeIgniter;
use CodeIgniter\Test\Mock\MockIncomingRequest;
use CodeIgniter\Test\Mock\MockSecurity;
use CodeIgniter\Test\Mock\MockSession;
use CodeIgniter\Test\TestLogger;
use Config\App;
use Config\Logger;
2021-02-28 06:57:05 +07:00
use Config\Modules;
use Config\Services;
2021-12-27 11:37:44 +09:00
use Kint;
2022-06-01 09:31:23 +08:00
use RuntimeException;
2021-02-28 08:28:54 +07:00
use stdClass;
2020-04-17 17:27:16 +07:00
use Tests\Support\Models\JobModel;
2018-06-07 02:56:46 -07:00
/**
* @backupGlobals enabled
*
* @internal
2022-05-05 15:37:25 +09:00
*
* @group SeparateProcess
*/
final class CommonFunctionsTest extends CIUnitTestCase
2016-07-12 10:11:17 -07:00
{
2022-06-27 18:24:58 +09:00
/**
2022-06-28 07:08:19 +09:00
* @var App
2022-06-27 18:24:58 +09:00
*/
2022-06-28 07:08:19 +09:00
private $config;
2022-06-27 18:24:58 +09:00
public MockIncomingRequest $request;
2021-06-04 22:51:52 +08:00
protected function setUp(): void
{
unset($_ENV['foo'], $_SERVER['foo']);
2021-12-02 16:06:58 +09:00
Services::reset();
parent::setUp();
2021-06-04 22:51:52 +08:00
}
public function testStringifyAttributes()
{
2021-06-25 23:35:25 +08:00
$this->assertSame(' class="foo" id="bar"', stringify_attributes(['class' => 'foo', 'id' => 'bar']));
2021-06-04 22:51:52 +08:00
$atts = new stdClass();
2021-06-04 22:51:52 +08:00
$atts->class = 'foo';
$atts->id = 'bar';
2021-06-25 23:35:25 +08:00
$this->assertSame(' class="foo" id="bar"', stringify_attributes($atts));
2021-06-04 22:51:52 +08:00
$atts = new stdClass();
2021-06-25 23:35:25 +08:00
$this->assertSame('', stringify_attributes($atts));
2021-06-04 22:51:52 +08:00
2021-06-25 23:35:25 +08:00
$this->assertSame(' class="foo" id="bar"', stringify_attributes('class="foo" id="bar"'));
2021-06-04 22:51:52 +08:00
2021-06-25 23:35:25 +08:00
$this->assertSame('', stringify_attributes([]));
2021-06-04 22:51:52 +08:00
}
public function testStringifyJsAttributes()
{
2021-06-25 23:35:25 +08:00
$this->assertSame('width=800,height=600', stringify_attributes(['width' => '800', 'height' => '600'], true));
2021-06-04 22:51:52 +08:00
$atts = new stdClass();
2021-06-04 22:51:52 +08:00
$atts->width = 800;
$atts->height = 600;
2021-06-25 23:35:25 +08:00
$this->assertSame('width=800,height=600', stringify_attributes($atts, true));
2021-06-04 22:51:52 +08:00
}
public function testEnvReturnsDefault()
{
2021-06-25 23:35:25 +08:00
$this->assertSame('baz', env('foo', 'baz'));
2021-06-04 22:51:52 +08:00
}
public function testEnvGetsFromSERVER()
{
$_SERVER['foo'] = 'bar';
2021-06-25 23:35:25 +08:00
$this->assertSame('bar', env('foo', 'baz'));
2021-06-04 22:51:52 +08:00
}
public function testEnvGetsFromENV()
{
$_ENV['foo'] = 'bar';
2021-06-25 23:35:25 +08:00
$this->assertSame('bar', env('foo', 'baz'));
2021-06-04 22:51:52 +08:00
}
public function testEnvBooleans()
{
$_ENV['p1'] = 'true';
$_ENV['p2'] = 'false';
$_ENV['p3'] = 'empty';
$_ENV['p4'] = 'null';
$this->assertTrue(env('p1'));
$this->assertFalse(env('p2'));
$this->assertEmpty(env('p3'));
$this->assertNull(env('p4'));
}
public function testRedirectReturnsRedirectResponse()
{
$_SERVER['REQUEST_METHOD'] = 'GET';
$response = $this->createMock(Response::class);
$routes = new RouteCollection(
2021-06-16 00:50:52 +08:00
Services::locator(),
new Modules()
2021-06-04 22:51:52 +08:00
);
Services::injectMock('response', $response);
Services::injectMock('routes', $routes);
2021-06-04 22:51:52 +08:00
$routes->add('home/base', 'Controller::index', ['as' => 'base']);
2021-06-24 23:29:53 +08:00
$response->method('redirect')->willReturnArgument(0);
2021-06-04 22:51:52 +08:00
$this->assertInstanceOf(RedirectResponse::class, redirect('base'));
}
public function testRedirectDefault()
{
$this->assertInstanceOf(RedirectResponse::class, redirect());
}
public function testView()
{
$data = [
2021-06-04 22:51:52 +08:00
'testString' => 'bar',
'bar' => 'baz',
];
$expected = '<h1>bar</h1>';
$this->assertStringContainsString($expected, view('\Tests\Support\View\Views\simple', $data));
}
public function testViewSavedData()
{
$data = [
2021-06-04 22:51:52 +08:00
'testString' => 'bar',
'bar' => 'baz',
];
$expected = '<h1>bar</h1>';
$this->assertStringContainsString($expected, view('\Tests\Support\View\Views\simple', $data, ['saveData' => true]));
$this->assertStringContainsString($expected, view('\Tests\Support\View\Views\simple'));
}
public function testViewCell()
{
$expected = 'Hello';
2021-06-25 23:35:25 +08:00
$this->assertSame($expected, view_cell('\Tests\Support\View\SampleClass::hello'));
2021-06-04 22:51:52 +08:00
}
public function testEscapeWithDifferentEncodings()
{
2021-06-25 23:35:25 +08:00
$this->assertSame('&lt;x', esc('<x', 'html', 'utf-8'));
$this->assertSame('&lt;x', esc('<x', 'html', 'iso-8859-1'));
$this->assertSame('&lt;x', esc('<x', 'html', 'windows-1251'));
2021-06-04 22:51:52 +08:00
}
public function testEscapeBadContext()
{
2022-03-21 01:39:09 +07:00
$this->expectException('InvalidArgumentException');
2021-06-04 22:51:52 +08:00
esc(['width' => '800', 'height' => '600'], 'bogus');
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testSessionInstance()
{
$this->injectSessionMock();
$this->assertInstanceOf(Session::class, session());
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testSessionVariable()
{
$this->injectSessionMock();
$_SESSION['notbogus'] = 'Hi there';
2021-06-25 23:35:25 +08:00
$this->assertSame('Hi there', session('notbogus'));
2021-06-04 22:51:52 +08:00
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testSessionVariableNotThere()
{
$this->injectSessionMock();
$_SESSION['bogus'] = 'Hi there';
$this->assertNull(session('notbogus'));
2021-06-04 22:51:52 +08:00
}
public function testRouteTo()
{
// prime the pump
$routes = service('routes');
$routes->add('path/(:any)/to/(:num)', 'myController::goto/$1/$2');
2021-06-25 23:35:25 +08:00
$this->assertSame('/path/string/to/13', route_to('myController::goto', 'string', 13));
2021-06-04 22:51:52 +08:00
}
public function testInvisible()
{
2021-06-25 23:35:25 +08:00
$this->assertSame('Javascript', remove_invisible_characters("Java\0script"));
2021-06-04 22:51:52 +08:00
}
public function testInvisibleEncoded()
{
2021-06-25 23:35:25 +08:00
$this->assertSame('Javascript', remove_invisible_characters('Java%0cscript'));
2021-06-04 22:51:52 +08:00
}
public function testAppTimezone()
{
2021-06-25 23:35:25 +08:00
$this->assertSame('America/Chicago', app_timezone());
2021-06-04 22:51:52 +08:00
}
public function testCSRFToken()
{
Services::injectMock('security', new MockSecurity(new App()));
2021-06-25 23:35:25 +08:00
$this->assertSame('csrf_test_name', csrf_token());
2021-06-04 22:51:52 +08:00
}
public function testCSRFHeader()
{
2021-06-25 23:35:25 +08:00
$this->assertSame('X-CSRF-TOKEN', csrf_header());
2021-06-04 22:51:52 +08:00
}
public function testHash()
{
2021-06-25 23:35:25 +08:00
$this->assertSame(32, strlen(csrf_hash()));
2021-06-04 22:51:52 +08:00
}
public function testCSRFField()
{
$this->assertStringContainsString('<input type="hidden" ', csrf_field());
}
public function testCSRFMeta()
{
$this->assertStringContainsString('<meta name="X-CSRF-TOKEN" ', csrf_meta());
}
public function testModelNotExists()
{
$this->assertNull(model(UnexsistenceClass::class));
}
public function testModelExistsBasename()
{
$this->assertInstanceOf(JobModel::class, model('JobModel'));
}
public function testModelExistsClassname()
{
$this->assertInstanceOf(JobModel::class, model(JobModel::class));
}
public function testModelExistsAbsoluteClassname()
{
2022-04-29 00:07:19 +07:00
$this->assertInstanceOf(JobModel::class, model(JobModel::class));
2021-06-04 22:51:52 +08:00
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testOldInput()
{
$this->injectSessionMock();
// setup from RedirectResponseTest...
$_SERVER['REQUEST_METHOD'] = 'GET';
$this->config = new App();
$this->config->baseURL = 'http://example.com/';
$this->routes = new RouteCollection(Services::locator(), new Modules());
Services::injectMock('routes', $this->routes);
$this->request = new MockIncomingRequest($this->config, new URI('http://example.com'), null, new UserAgent());
Services::injectMock('request', $this->request);
// setup & ask for a redirect...
$_SESSION = [];
$_GET = ['foo' => 'bar'];
$_POST = [
'bar' => 'baz',
'zibble' => 'fritz',
2021-06-04 22:51:52 +08:00
];
$response = new RedirectResponse(new App());
$response->withInput();
2021-06-04 22:51:52 +08:00
2021-06-25 23:35:25 +08:00
$this->assertSame('bar', old('foo')); // regular parameter
$this->assertSame('doo', old('yabba dabba', 'doo')); // non-existing parameter
2021-11-12 14:08:04 +09:00
$this->assertSame('fritz', old('zibble'));
2021-06-04 22:51:52 +08:00
}
2021-11-12 14:09:37 +09:00
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testOldInputSerializeData()
{
$this->injectSessionMock();
// setup from RedirectResponseTest...
$_SERVER['REQUEST_METHOD'] = 'GET';
$this->config = new App();
$this->config->baseURL = 'http://example.com/';
$this->routes = new RouteCollection(Services::locator(), new Modules());
Services::injectMock('routes', $this->routes);
$this->request = new MockIncomingRequest($this->config, new URI('http://example.com'), null, new UserAgent());
Services::injectMock('request', $this->request);
// setup & ask for a redirect...
$_SESSION = [];
$_GET = [];
$_POST = [
'zibble' => serialize('fritz'),
];
$response = new RedirectResponse(new App());
$response->withInput();
// serialized parameters are only HTML-escaped.
$this->assertSame('s:5:&quot;fritz&quot;;', old('zibble'));
2021-06-04 22:51:52 +08:00
}
/**
* @see https://github.com/codeigniter4/CodeIgniter4/issues/1492
2021-06-04 22:51:52 +08:00
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testOldInputArray()
{
$this->injectSessionMock();
// setup from RedirectResponseTest...
$_SERVER['REQUEST_METHOD'] = 'GET';
$this->config = new App();
$this->config->baseURL = 'http://example.com/';
$this->routes = new RouteCollection(Services::locator(), new Modules());
Services::injectMock('routes', $this->routes);
$this->request = new MockIncomingRequest($this->config, new URI('http://example.com'), null, new UserAgent());
Services::injectMock('request', $this->request);
$locations = [
'AB' => 'Alberta',
'BC' => 'British Columbia',
'SK' => 'Saskatchewan',
];
// setup & ask for a redirect...
$_SESSION = [];
$_GET = [];
$_POST = ['location' => $locations];
$response = new RedirectResponse(new App());
$response->withInput();
2021-06-04 22:51:52 +08:00
2021-06-25 23:35:25 +08:00
$this->assertSame($locations, old('location'));
2021-06-04 22:51:52 +08:00
}
public function testReallyWritable()
{
// cannot test fully on *nix
$this->assertTrue(is_really_writable(WRITEPATH));
}
public function testSlashItem()
{
2022-06-01 09:31:23 +08:00
$this->assertSame('/', slash_item('cookiePath')); // /
$this->assertSame('', slash_item('cookieDomain')); // ''
$this->assertSame('en/', slash_item('defaultLocale')); // en
$this->assertSame('7200/', slash_item('sessionExpiration')); // int 7200
$this->assertSame('', slash_item('negotiateLocale')); // false
$this->assertSame('1/', slash_item('cookieHTTPOnly')); // true
}
public function testSlashItemOnInexistentItem()
{
$this->assertNull(slash_item('foo'));
$this->assertNull(slash_item('bar'));
$this->assertNull(slash_item('cookieDomains'));
$this->assertNull(slash_item('indices'));
}
public function testSlashItemThrowsErrorOnNonStringableItem()
{
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Cannot convert "Config\\App::$supportedLocales" of type "array" to type "string".');
slash_item('supportedLocales');
2021-06-04 22:51:52 +08:00
}
protected function injectSessionMock()
{
$defaults = [
'sessionDriver' => FileHandler::class,
2021-06-04 22:51:52 +08:00
'sessionCookieName' => 'ci_session',
'sessionExpiration' => 7200,
'sessionSavePath' => null,
'sessionMatchIP' => false,
'sessionTimeToUpdate' => 300,
'sessionRegenerateDestroy' => false,
'cookieDomain' => '',
'cookiePrefix' => '',
'cookiePath' => '/',
'cookieSecure' => false,
'cookieSameSite' => 'Lax',
];
$appConfig = new App();
2021-06-07 19:06:26 +08:00
foreach ($defaults as $key => $config) {
$appConfig->{$key} = $config;
2021-06-04 22:51:52 +08:00
}
$session = new MockSession(new FileHandler($appConfig, '127.0.0.1'), $appConfig);
$session->setLogger(new TestLogger(new Logger()));
BaseService::injectMock('session', $session);
}
// Make sure cookies are set by RedirectResponse this way
// See https://github.com/codeigniter4/CodeIgniter4/issues/1393
public function testRedirectResponseCookies1()
{
$loginTime = time();
$routes = service('routes');
$routes->add('user/login', 'Auth::verify', ['as' => 'login']);
$answer1 = redirect()->route('login')
->setCookie('foo', 'onething', YEAR)
->setCookie('login_time', $loginTime, YEAR);
2021-06-04 22:51:52 +08:00
$this->assertTrue($answer1->hasCookie('foo', 'onething'));
$this->assertTrue($answer1->hasCookie('login_time'));
}
2022-01-12 21:27:11 +09:00
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
2021-06-04 22:51:52 +08:00
public function testTrace()
{
ob_start();
trace();
$content = ob_get_clean();
$this->assertStringContainsString('Debug Backtrace', $content);
}
public function testViewNotSaveData()
{
$data = [
'testString' => 'bar',
'bar' => 'baz',
];
$this->assertStringContainsString('<h1>bar</h1>', view('\Tests\Support\View\Views\simples', $data, ['saveData' => false]));
$this->assertStringContainsString('<h1>is_not</h1>', view('\Tests\Support\View\Views\simples'));
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testForceHttpsNullRequestAndResponse()
{
$this->assertNull(Services::response()->header('Location'));
force_https();
2021-06-25 23:35:25 +08:00
$this->assertSame('https://example.com/', Services::response()->header('Location')->getValue());
2021-06-04 22:51:52 +08:00
}
/**
* @dataProvider dirtyPathsProvider
2021-07-24 19:30:51 +08:00
*
* @param mixed $input
* @param mixed $expected
2021-06-04 22:51:52 +08:00
*/
public function testCleanPathActuallyCleaningThePaths($input, $expected)
{
2021-06-25 23:35:25 +08:00
$this->assertSame($expected, clean_path($input));
2021-06-04 22:51:52 +08:00
}
public function dirtyPathsProvider()
{
$ds = DIRECTORY_SEPARATOR;
return [
[
ROOTPATH . 'spark',
'ROOTPATH' . $ds . 'spark',
],
[
APPPATH . 'Config' . $ds . 'App.php',
'APPPATH' . $ds . 'Config' . $ds . 'App.php',
],
[
SYSTEMPATH . 'CodeIgniter.php',
'SYSTEMPATH' . $ds . 'CodeIgniter.php',
],
[
VENDORPATH . 'autoload.php',
'VENDORPATH' . $ds . 'autoload.php',
],
[
FCPATH . 'index.php',
'FCPATH' . $ds . 'index.php',
],
];
}
public function testIsCli()
{
$this->assertIsBool(is_cli());
$this->assertTrue(is_cli());
}
2021-12-27 11:37:44 +09:00
public function testDWithCSP()
{
$this->resetServices();
2021-12-27 11:37:44 +09:00
/** @var App $config */
2022-02-05 13:55:03 +09:00
$config = config('App');
$config->CSPEnabled = true;
2021-12-27 11:37:44 +09:00
2022-02-05 13:55:03 +09:00
// Initialize Kint
$app = new MockCodeIgniter($config);
$app->initialize();
$cliDetection = Kint::$cli_detection;
2021-12-27 11:37:44 +09:00
Kint::$cli_detection = false;
$this->expectOutputRegex('/<script class="kint-rich-script" nonce="[0-9a-z]{24}">/u');
2021-12-27 11:37:44 +09:00
d('string');
// Restore settings
Kint::$cli_detection = $cliDetection;
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testTraceWithCSP()
{
$this->resetServices();
2021-12-27 11:37:44 +09:00
/** @var App $config */
2022-02-05 13:55:03 +09:00
$config = config('App');
$config->CSPEnabled = true;
// Initialize Kint
$app = new MockCodeIgniter($config);
$app->initialize();
2021-12-27 11:37:44 +09:00
Kint::$cli_detection = false;
$this->expectOutputRegex('/<style class="kint-rich-style" nonce="[0-9a-z]{24}">/u');
2021-12-27 11:37:44 +09:00
trace();
}
public function testCspStyleNonce()
{
$this->resetServices();
$config = config('App');
$config->CSPEnabled = true;
2022-01-06 12:58:22 +09:00
$this->assertStringStartsWith('nonce="', csp_style_nonce());
}
public function testCspScriptNonce()
{
$this->resetServices();
$config = config('App');
$config->CSPEnabled = true;
2022-01-06 12:58:22 +09:00
$this->assertStringStartsWith('nonce="', csp_script_nonce());
}
2016-07-12 10:11:17 -07:00
}