Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions system/Filters/PageCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use CodeIgniter\Cache\ResponseCache;
use CodeIgniter\HTTP\CLIRequest;
use CodeIgniter\HTTP\IncomingRequest;
use CodeIgniter\HTTP\Method;
use CodeIgniter\HTTP\NonBufferedResponseInterface;
use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\HTTP\RequestInterface;
Expand Down Expand Up @@ -53,6 +54,10 @@ public function before(RequestInterface $request, $arguments = null)
{
assert($request instanceof CLIRequest || $request instanceof IncomingRequest);

if ($request instanceof IncomingRequest && $request->getMethod() === Method::QUERY) {
return null;
}

$response = service('response');

return $this->pageCache->get($request, $response);
Expand All @@ -67,6 +72,10 @@ public function after(RequestInterface $request, ResponseInterface $response, $a
{
assert($request instanceof CLIRequest || $request instanceof IncomingRequest);

if ($request instanceof IncomingRequest && $request->getMethod() === Method::QUERY) {
return null;
}

if (
! $response instanceof NonBufferedResponseInterface
&& ! $response instanceof RedirectResponse
Expand Down
10 changes: 10 additions & 0 deletions system/HTTP/CURLRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,16 @@ public function put(string $url, array $options = []): ResponseInterface
return $this->request(Method::PUT, $url, $options);
}

/**
* Convenience method for sending a QUERY request.
*
* @param array<string, mixed> $options
*/
public function query(string $url, array $options = []): ResponseInterface
{
return $this->request(Method::QUERY, $url, $options);
}

/**
* Set the HTTP Authentication.
*
Expand Down
10 changes: 5 additions & 5 deletions system/HTTP/FormRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,10 @@ public function getValidatedInput(): ValidatedInput
* multiple sources. By default, data is sourced from the appropriate
* part of the request based on HTTP method and Content-Type:
*
* - JSON (any method) - decoded JSON body
* - PUT / PATCH / DELETE - raw body (unless multipart/form-data)
* - GET / HEAD - query-string parameters
* - Everything else (POST) - POST body
* - JSON (any method) - decoded JSON body
* - PUT / PATCH / DELETE / QUERY - raw body (unless multipart/form-data)
* - GET / HEAD - query-string parameters
* - Everything else (POST) - POST body
*
* @return array<string, mixed>
*/
Expand All @@ -239,7 +239,7 @@ protected function validationData(): array
}

if (
in_array($this->request->getMethod(), [Method::PUT, Method::PATCH, Method::DELETE], true)
in_array($this->request->getMethod(), [Method::PUT, Method::PATCH, Method::DELETE, Method::QUERY], true)
&& ! str_contains($contentType, 'multipart/form-data')
) {
return $this->request->getRawInput() ?? [];
Expand Down
4 changes: 2 additions & 2 deletions system/HTTP/IncomingRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ public function getJsonVar($index = null, bool $assoc = false, ?int $filter = nu
}

/**
* A convenience method that grabs the raw input stream(send method in PUT, PATCH, DELETE) and decodes
* A convenience method that grabs the raw input stream (send method in PUT, PATCH, DELETE, QUERY) and decodes
* the String into an array.
*
* @return array
Expand All @@ -516,7 +516,7 @@ public function getRawInput()
}

/**
* Gets a specific variable from raw input stream (send method in PUT, PATCH, DELETE).
* Gets a specific variable from raw input stream (send method in PUT, PATCH, DELETE, QUERY).
*
* @param array|string|null $index The variable that you want which can use dot syntax for getting specific values.
* @param int|null $filter Filter Constant
Expand Down
10 changes: 10 additions & 0 deletions system/HTTP/Method.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@ class Method
*/
public const PUT = 'PUT';

/**
* Safe: Yes
* Idempotent: Yes
* Cacheable: Yes
*
* @see https://www.rfc-editor.org/rfc/rfc10008.html
*/
public const QUERY = 'QUERY';

/**
* Safe: Yes
* Idempotent: Yes
Expand All @@ -115,6 +124,7 @@ public static function all(): array
self::PATCH,
self::POST,
self::PUT,
self::QUERY,
self::TRACE,
];
}
Expand Down
15 changes: 15 additions & 0 deletions system/Router/RouteCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ class RouteCollection implements RouteCollectionInterface
Method::OPTIONS => [],
Method::GET => [],
Method::HEAD => [],
Method::QUERY => [],
Method::POST => [],
Method::PATCH => [],
Method::PUT => [],
Expand All @@ -168,6 +169,7 @@ class RouteCollection implements RouteCollectionInterface
Method::OPTIONS => [],
Method::GET => [],
Method::HEAD => [],
Method::QUERY => [],
Method::POST => [],
Method::PATCH => [],
Method::PUT => [],
Expand Down Expand Up @@ -1102,6 +1104,19 @@ public function head(string $from, $to, ?array $options = null): RouteCollection
return $this;
}

/**
* Specifies a route that is only available to QUERY requests.
*
* @param array<string, mixed>|(Closure(mixed...): (ResponseInterface|string|void))|string $to
* @param array<string, mixed>|null $options
*/
public function query(string $from, $to, ?array $options = null): RouteCollectionInterface
{
$this->create(Method::QUERY, $from, $to, $options);

return $this;
}

/**
* Specifies a route that is only available to PATCH requests.
*
Expand Down
1 change: 1 addition & 0 deletions system/Router/Router.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class Router implements RouterInterface
public const HTTP_METHODS = [
Method::GET,
Method::HEAD,
Method::QUERY,
Comment thread
memleakd marked this conversation as resolved.
Method::POST,
Method::PATCH,
Method::PUT,
Expand Down
15 changes: 15 additions & 0 deletions system/Test/FeatureTestTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,21 @@ public function options(string $path, ?array $params = null)
return $this->call(Method::OPTIONS, $path, $params);
}

/**
* Performs a QUERY request.
*
* @param array<string, mixed>|null $params
*
* @return TestResponse
*
* @throws RedirectException
* @throws Exception
*/
public function query(string $path, ?array $params = null)
{
return $this->call(Method::QUERY, $path, $params);
}

/**
* Setup a Request object to use so that CodeIgniter
* won't try to auto-populate some of the items.
Expand Down
2 changes: 1 addition & 1 deletion system/Validation/Validation.php
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ public function withRequest(RequestInterface $request): ValidationInterface
return $this;
}

if (in_array($request->getMethod(), [Method::PUT, Method::PATCH, Method::DELETE], true)
if (in_array($request->getMethod(), [Method::PUT, Method::PATCH, Method::DELETE, Method::QUERY], true)
&& ! str_contains($request->getHeaderLine('Content-Type'), 'multipart/form-data')
) {
$this->data = $request->getRawInput();
Expand Down
10 changes: 10 additions & 0 deletions tests/system/Commands/Utilities/RoutesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ protected function setUp(): void
{
$this->resetServices();
parent::setUp();

service('superglobals')
->setServer('HTTP_HOST', 'example.com')
->setServer('SERVER_NAME', 'example.com');
}

protected function tearDown(): void
Expand Down Expand Up @@ -68,6 +72,7 @@ public function testRoutesCommand(): void
| GET | closure | » | (Closure) | | |
| GET | testing | testing-index | \App\Controllers\TestController::index | | |
| HEAD | testing | testing-index | \App\Controllers\TestController::index | | |
| QUERY | testing | testing-index | \App\Controllers\TestController::index | | |
| POST | testing | testing-index | \App\Controllers\TestController::index | | |
| PATCH | testing | testing-index | \App\Controllers\TestController::index | | |
| PUT | testing | testing-index | \App\Controllers\TestController::index | | |
Expand Down Expand Up @@ -104,6 +109,7 @@ public function testRoutesCommandSortByHandler(): void
| GET | / | » | \App\Controllers\Home::index | | |
| GET | testing | testing-index | \App\Controllers\TestController::index | | |
| HEAD | testing | testing-index | \App\Controllers\TestController::index | | |
| QUERY | testing | testing-index | \App\Controllers\TestController::index | | |
| POST | testing | testing-index | \App\Controllers\TestController::index | | |
| PATCH | testing | testing-index | \App\Controllers\TestController::index | | |
| PUT | testing | testing-index | \App\Controllers\TestController::index | | |
Expand Down Expand Up @@ -133,6 +139,7 @@ public function testRoutesCommandHostHostname(): void
| GET | all | » | \App\Controllers\AllDomain::index | | |
| GET | testing | testing-index | \App\Controllers\TestController::index | | |
| HEAD | testing | testing-index | \App\Controllers\TestController::index | | |
| QUERY | testing | testing-index | \App\Controllers\TestController::index | | |
| POST | testing | testing-index | \App\Controllers\TestController::index | | |
| PATCH | testing | testing-index | \App\Controllers\TestController::index | | |
| PUT | testing | testing-index | \App\Controllers\TestController::index | | |
Expand Down Expand Up @@ -162,6 +169,7 @@ public function testRoutesCommandHostSubdomain(): void
| GET | all | » | \App\Controllers\AllDomain::index | | |
| GET | testing | testing-index | \App\Controllers\TestController::index | | |
| HEAD | testing | testing-index | \App\Controllers\TestController::index | | |
| QUERY | testing | testing-index | \App\Controllers\TestController::index | | |
| POST | testing | testing-index | \App\Controllers\TestController::index | | |
| PATCH | testing | testing-index | \App\Controllers\TestController::index | | |
| PUT | testing | testing-index | \App\Controllers\TestController::index | | |
Expand Down Expand Up @@ -193,6 +201,7 @@ public function testRoutesCommandAutoRouteImproved(): void
| GET | closure | » | (Closure) | | |
| GET | testing | testing-index | \App\Controllers\TestController::index | | |
| HEAD | testing | testing-index | \App\Controllers\TestController::index | | |
| QUERY | testing | testing-index | \App\Controllers\TestController::index | | |
| POST | testing | testing-index | \App\Controllers\TestController::index | | |
| PATCH | testing | testing-index | \App\Controllers\TestController::index | | |
| PUT | testing | testing-index | \App\Controllers\TestController::index | | |
Expand Down Expand Up @@ -228,6 +237,7 @@ public function testRoutesCommandRouteLegacy(): void
| GET | closure | » | (Closure) | | |
| GET | testing | testing-index | \App\Controllers\TestController::index | | |
| HEAD | testing | testing-index | \App\Controllers\TestController::index | | |
| QUERY | testing | testing-index | \App\Controllers\TestController::index | | |
| POST | testing | testing-index | \App\Controllers\TestController::index | | |
| PATCH | testing | testing-index | \App\Controllers\TestController::index | | |
| PUT | testing | testing-index | \App\Controllers\TestController::index | | |
Expand Down
39 changes: 37 additions & 2 deletions tests/system/Filters/PageCacheTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

use CodeIgniter\HTTP\DownloadResponse;
use CodeIgniter\HTTP\IncomingRequest;
use CodeIgniter\HTTP\Method;
use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\HTTP\Response;
use CodeIgniter\HTTP\ResponseInterface;
Expand All @@ -31,14 +32,15 @@
#[Group('Others')]
final class PageCacheTest extends CIUnitTestCase
{
private function createRequest(): IncomingRequest
private function createRequest(string $method = Method::GET): IncomingRequest
{
$superglobals = service('superglobals');
$superglobals->setServer('REQUEST_URI', '/');

$siteUri = new SiteURI(new App());

return new IncomingRequest(new App(), $siteUri, null, new UserAgent());
return (new IncomingRequest(new App(), $siteUri, null, new UserAgent()))
->withMethod($method);
}

public function testDefaultConfigCachesAllStatusCodes(): void
Expand Down Expand Up @@ -141,6 +143,39 @@ public function testCustomCacheStatusCodes(): void
$this->assertNotInstanceOf(ResponseInterface::class, $result);
}

public function testQueryRequestIsNotCached(): void
{
$config = new Cache();
$filter = new PageCache($config);

$request = $this->createRequest(Method::QUERY);
$response = new Response();
$response->setStatusCode(200);
$response->setBody('Success');

$result = $filter->after($request, $response);
$this->assertNotInstanceOf(ResponseInterface::class, $result);
}

public function testQueryRequestDoesNotReturnCachedResponse(): void
{
$filter = new PageCache(new Cache());
$request = $this->createRequest(Method::QUERY);
$key = service('responsecache')->generateCacheKey($request);

service('cache')->save($key, serialize([
'headers' => [],
'output' => 'Cached',
'status' => 200,
'reason' => 'OK',
]), 60);

$result = $filter->before($request);
$this->assertNotInstanceOf(ResponseInterface::class, $result);

service('cache')->delete($key);
}

public function testDownloadResponseNotCached(): void
{
$config = new Cache();
Expand Down
29 changes: 29 additions & 0 deletions tests/system/HTTP/CURLRequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,35 @@ public function testOptionsSetsCorrectMethod(): void
$this->assertSame('OPTIONS', $options[CURLOPT_CUSTOMREQUEST]);
}

public function testQuerySetsCorrectMethod(): void
{
$this->request->query('http://example.com');

$this->assertSame('QUERY', $this->request->getMethod());

$options = $this->request->curl_options;

$this->assertArrayHasKey(CURLOPT_CUSTOMREQUEST, $options);
$this->assertSame('QUERY', $options[CURLOPT_CUSTOMREQUEST]);
}

public function testQueryWithJsonSetsBodyAndContentType(): void
{
$params = [
'foo' => 'bar',
];
$this->request->query('http://example.com', [
'json' => $params,
]);

$options = $this->request->curl_options;

$this->assertSame('QUERY', $this->request->getMethod());
$this->assertSame('QUERY', $options[CURLOPT_CUSTOMREQUEST]);
$this->assertSame(json_encode($params), $options[CURLOPT_POSTFIELDS]);
$this->assertContains('Content-Type: application/json', $options[CURLOPT_HTTPHEADER]);
}

public function testOptionsBaseURIOption(): void
{
$options = ['baseURI' => 'http://www.foo.com/api/v1/'];
Expand Down
19 changes: 18 additions & 1 deletion tests/system/HTTP/FormRequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ protected function setUp(): void
parent::setUp();

$this->resetServices();
Services::injectMock('superglobals', new Superglobals());
Services::injectMock('superglobals', new Superglobals(get: [], post: []));
service('superglobals')->setServer('REQUEST_METHOD', 'POST');
service('superglobals')->setServer('SERVER_PROTOCOL', 'HTTP/1.1');
service('superglobals')->setServer('SERVER_NAME', 'example.com');
Expand Down Expand Up @@ -346,6 +346,23 @@ public function testResolveRequestReturns422ForJsonRequest(): void
$this->assertSame(422, $response->getStatusCode());
}

public function testResolveRequestUsesQueryRawBody(): void
{
service('superglobals')
->setServer('REQUEST_METHOD', Method::QUERY)
->setServer('CONTENT_TYPE', 'application/x-www-form-urlencoded');

$formRequest = $this->makeFormRequest($this->makeRequest('title=Hello&body=World'));

$response = $formRequest->resolveRequest();

$this->assertNotInstanceOf(ResponseInterface::class, $response);
$this->assertSame([
'title' => 'Hello',
'body' => 'World',
], $formRequest->getValidated());
}

public function testResolveRequestReturns422WhenJsonIsPreferred(): void
{
service('superglobals')->setServer('HTTP_ACCEPT', 'application/json');
Expand Down
1 change: 1 addition & 0 deletions tests/system/HTTP/IncomingRequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,7 @@ public static function provideIsHTTPMethods(): iterable
['HEAD'],
['PATCH'],
['OPTIONS'],
['QUERY'],
];
}

Expand Down
Loading
Loading