PHP Version
8.4
CodeIgniter4 Version
4.6.4
CodeIgniter4 Installation Method
Composer (using codeigniter4/appstarter)
Which operating systems have you tested for this bug?
macOS
Which server did you use?
cli-server (PHP built-in webserver)
Environment
development
Database
What happened?
In some cases, using $validator->withRequest() will not work correctly. Let's consider this test:
|
public function testQueryStringWithQueryString(): void |
|
{ |
|
// /index.php?/ci/woot?code=good#pos |
|
$_SERVER['REQUEST_URI'] = '/index.php?/ci/woot?code=good'; |
|
$_SERVER['QUERY_STRING'] = '/ci/woot?code=good'; |
|
$_SERVER['SCRIPT_NAME'] = '/index.php'; |
|
|
|
$_GET['/ci/woot?code'] = 'good'; |
|
|
|
$factory = $this->createSiteURIFactory($_SERVER); |
|
|
|
$expected = 'ci/woot'; |
|
$this->assertSame($expected, $factory->detectRoutePath('QUERY_STRING')); |
|
$this->assertSame('code=good', $_SERVER['QUERY_STRING']); |
|
$this->assertSame(['code' => 'good'], $_GET); |
|
} |
As you may notice in the above test, SiteURIFactory updates some superglobals, here:
|
if (trim($path, '/') === '' && str_starts_with($query, '/')) { |
|
$parts = explode('?', $query, 2); |
|
$path = $parts[0]; |
|
$newQuery = $query[1] ?? ''; |
|
|
|
$this->superglobals->setServer('QUERY_STRING', $newQuery); |
|
} else { |
|
$this->superglobals->setServer('QUERY_STRING', $query); |
|
} |
|
|
|
// Update our global GET for values likely to have been changed |
|
parse_str($this->superglobals->server('QUERY_STRING'), $get); |
|
$this->superglobals->setGetArray($get); |
|
|
The problem is that while $_SERVER['QUERY_STRING'] and $_GET are being updated, the $_REQUEST isn't. And $_REQUEST is needed to make getVar() work correctly.
|
public function withRequest(RequestInterface $request): ValidationInterface |
|
{ |
|
/** @var IncomingRequest $request */ |
|
if (str_contains($request->getHeaderLine('Content-Type'), 'application/json')) { |
|
$this->data = $request->getJSON(true); |
|
|
|
if (! is_array($this->data)) { |
|
throw HTTPException::forUnsupportedJSONFormat(); |
|
} |
|
|
|
return $this; |
|
} |
|
|
|
if (in_array($request->getMethod(), [Method::PUT, Method::PATCH, Method::DELETE], true) |
|
&& ! str_contains($request->getHeaderLine('Content-Type'), 'multipart/form-data') |
|
) { |
|
$this->data = $request->getRawInput(); |
|
} else { |
|
$this->data = $request->getVar() ?? []; |
|
} |
|
|
|
return $this; |
|
} |
|
public function getVar($index = null, $filter = null, $flags = null) |
|
{ |
|
if ( |
|
str_contains($this->getHeaderLine('Content-Type'), 'application/json') |
|
&& $this->body !== null |
|
) { |
|
return $this->getJsonVar($index, false, $filter, $flags); |
|
} |
|
|
|
return $this->fetchGlobal('request', $index, $filter, $flags); |
|
} |
Steps to Reproduce
public function testQueryStringWithQueryStringAndRequest(): void
{
// /index.php?/ci/woot?code=good#pos
$_SERVER['REQUEST_URI'] = '/index.php?/ci/woot?code=good';
$_SERVER['QUERY_STRING'] = '/ci/woot?code=good';
$_SERVER['SCRIPT_NAME'] = '/index.php';
// these are always the same at the beginning
$_GET['/ci/woot?code'] = 'good';
$_REQUEST['/ci/woot?code'] = 'good';
$factory = $this->createSiteURIFactory($_SERVER);
$expected = 'ci/woot';
$this->assertSame($expected, $factory->detectRoutePath('QUERY_STRING'));
$this->assertSame('code=good', $_SERVER['QUERY_STRING']);
$this->assertSame(['code' => 'good'], $_GET);
// this will fail
$this->assertSame(['code' => 'good'], $_REQUEST);
}
Expected Output
Validation method $validator->withRequest() should work correctly.
The most tempting solution is to add syncRequestAfterGetChange() to Superglobals and call it from SiteURIFactory whenever $_GET is modified. This would keep $_REQUEST synchronized with current values.
The downside is it introduces "magic" behavior that violates standard PHP semantics where $_REQUEST is populated once and never auto-updated. However, since we update $_GET as a "standard", behavior, then updating $_REQUEST may be acceptable?
I was thinking about something like this:
public function syncRequestAfterGetChange(): void
{
$requestOrder = ini_get('request_order') ?: ini_get('variables_order');
$this->request = [];
foreach (str_split($requestOrder) as $type) {
match ($type) {
'G' => $this->request = array_merge($this->request, $this->get),
'P' => $this->request = array_merge($this->request, $this->post),
'C' => $this->request = array_merge($this->request, $this->cookie),
default => null,
};
}
$_REQUEST = $this->request;
}
But I'm unsure if this is the right direction.
Anything else?
https://forum.codeigniter.com/showthread.php?tid=93652
PHP Version
8.4
CodeIgniter4 Version
4.6.4
CodeIgniter4 Installation Method
Composer (using
codeigniter4/appstarter)Which operating systems have you tested for this bug?
macOS
Which server did you use?
cli-server (PHP built-in webserver)
Environment
development
Database
What happened?
In some cases, using
$validator->withRequest()will not work correctly. Let's consider this test:CodeIgniter4/tests/system/HTTP/SiteURIFactoryDetectRoutePathTest.php
Lines 226 to 241 in 11f0130
As you may notice in the above test,
SiteURIFactoryupdates some superglobals, here:CodeIgniter4/system/HTTP/SiteURIFactory.php
Lines 162 to 175 in 11f0130
The problem is that while
$_SERVER['QUERY_STRING']and$_GETare being updated, the$_REQUESTisn't. And$_REQUESTis needed to makegetVar()work correctly.CodeIgniter4/system/Validation/Validation.php
Lines 505 to 527 in 11f0130
CodeIgniter4/system/HTTP/IncomingRequest.php
Lines 496 to 506 in 11f0130
Steps to Reproduce
Expected Output
Validation method
$validator->withRequest()should work correctly.The most tempting solution is to add
syncRequestAfterGetChange()toSuperglobalsand call it fromSiteURIFactorywhenever$_GETis modified. This would keep$_REQUESTsynchronized with current values.The downside is it introduces "magic" behavior that violates standard PHP semantics where
$_REQUESTis populated once and never auto-updated. However, since we update$_GETas a "standard", behavior, then updating$_REQUESTmay be acceptable?I was thinking about something like this:
But I'm unsure if this is the right direction.
Anything else?
https://forum.codeigniter.com/showthread.php?tid=93652