diff --git a/system/HTTP/SiteURIFactory.php b/system/HTTP/SiteURIFactory.php index 11dccce6c540..a3af7589c215 100644 --- a/system/HTTP/SiteURIFactory.php +++ b/system/HTTP/SiteURIFactory.php @@ -173,6 +173,10 @@ private function parseRequestURI(): string parse_str($this->superglobals->server('QUERY_STRING'), $get); $this->superglobals->setGetArray($get); + // Keep $_REQUEST in sync with the parsed GET values, as getVar() and + // validation read from $_REQUEST rather than $_GET. + $this->syncRequestWithGet($get); + return URI::removeDotSegments($path); } @@ -205,9 +209,37 @@ private function parseQueryString(): string parse_str($this->superglobals->server('QUERY_STRING'), $get); $this->superglobals->setGetArray($get); + // Keep $_REQUEST in sync with the parsed GET values, as getVar() and + // validation read from $_REQUEST rather than $_GET. + $this->syncRequestWithGet($get); + return URI::removeDotSegments($path); } + /** + * Keep $_REQUEST in sync with the latest parsed GET values. + * + * `getVar()` and validation read from $_REQUEST rather than $_GET, so when + * the query string is rewritten here (e.g. `/index.php?/ci/woot?code=good#pos`), + * the GET-originated keys must be reflected in $_REQUEST too. Otherwise + * `getVar('code')` returns the stale or unset value. + * + * Only GET-originated keys are updated; values from POST, COOKIE and other + * sources are left untouched. + * + * @param array $get The parsed GET values + */ + private function syncRequestWithGet(array $get): void + { + $request = $this->superglobals->getRequestArray(); + + foreach ($get as $key => $value) { + $request[$key] = $value; + } + + $this->superglobals->setRequestArray($request); + } + /** * Create current URI object. * diff --git a/tests/system/HTTP/SiteURIFactoryDetectRoutePathTest.php b/tests/system/HTTP/SiteURIFactoryDetectRoutePathTest.php index 4bc29fbecc4d..6df484779c16 100644 --- a/tests/system/HTTP/SiteURIFactoryDetectRoutePathTest.php +++ b/tests/system/HTTP/SiteURIFactoryDetectRoutePathTest.php @@ -258,6 +258,25 @@ public function testQueryStringWithQueryString(): void $this->assertSame(['code' => 'good'], $_GET); } + public function testQueryStringKeepsRequestInSyncWithGet(): void + { + // /index.php?/ci/woot?code=good#pos + service('superglobals') + ->setServer('REQUEST_URI', '/index.php?/ci/woot?code=good') + ->setServer('QUERY_STRING', '/ci/woot?code=good') + ->setServer('SCRIPT_NAME', '/index.php') + ->setGet('/ci/woot?code', 'good'); + + $factory = $this->createSiteURIFactory(service('superglobals')->getServerArray()); + + $expected = 'ci/woot'; + $this->assertSame($expected, $factory->detectRoutePath('QUERY_STRING')); + + // getVar() reads from $_REQUEST, so the corrected GET value must be + // reflected there too, not just in $_GET. + $this->assertSame(['code' => 'good'], $_REQUEST); // @phpstan-ignore codeigniter.superglobalsOffsetAccess (checks the live superglobal written by the factory) + } + public function testQueryStringEmpty(): void { // /index.php?