Initial commit
This commit is contained in:
commit
efa5624dab
687 changed files with 162710 additions and 0 deletions
434
kirby/src/Http/Request.php
Normal file
434
kirby/src/Http/Request.php
Normal file
|
|
@ -0,0 +1,434 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Http;
|
||||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Http\Request\Auth;
|
||||
use Kirby\Http\Request\Auth\BasicAuth;
|
||||
use Kirby\Http\Request\Auth\BearerAuth;
|
||||
use Kirby\Http\Request\Auth\SessionAuth;
|
||||
use Kirby\Http\Request\Body;
|
||||
use Kirby\Http\Request\Files;
|
||||
use Kirby\Http\Request\Query;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
* The `Request` class provides
|
||||
* a simple API to inspect incoming
|
||||
* requests.
|
||||
*
|
||||
* @package Kirby Http
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*/
|
||||
class Request
|
||||
{
|
||||
public static array $authTypes = [
|
||||
'basic' => BasicAuth::class,
|
||||
'bearer' => BearerAuth::class,
|
||||
'session' => SessionAuth::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* The auth object if available
|
||||
*/
|
||||
protected Auth|false|null $auth = null;
|
||||
|
||||
/**
|
||||
* The Body object is a wrapper around
|
||||
* the request body, which parses the contents
|
||||
* of the body and provides an API to fetch
|
||||
* particular parts of the body
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* `$request->body()->get('foo')`
|
||||
*/
|
||||
protected Body|null $body = null;
|
||||
|
||||
/**
|
||||
* The Files object is a wrapper around
|
||||
* the $_FILES global. It sanitizes the
|
||||
* $_FILES array and provides an API to fetch
|
||||
* individual files by key
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* `$request->files()->get('upload')['size']`
|
||||
* `$request->file('upload')['size']`
|
||||
*/
|
||||
protected Files|null $files = null;
|
||||
|
||||
/**
|
||||
* The Method type
|
||||
*/
|
||||
protected string $method;
|
||||
|
||||
/**
|
||||
* The Query object is a wrapper around
|
||||
* the URL query string, which parses the
|
||||
* string and provides a clean API to fetch
|
||||
* particular parts of the query
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* `$request->query()->get('foo')`
|
||||
*/
|
||||
protected Query $query;
|
||||
|
||||
/**
|
||||
* Request URL object
|
||||
*/
|
||||
protected Uri $url;
|
||||
|
||||
/**
|
||||
* Creates a new Request object
|
||||
* You can either pass your own request
|
||||
* data via the $options array or use
|
||||
* the data from the incoming request.
|
||||
*/
|
||||
public function __construct(
|
||||
protected array $options = []
|
||||
) {
|
||||
$this->method = $this->detectRequestMethod($options['method'] ?? null);
|
||||
|
||||
if (isset($options['body']) === true) {
|
||||
$this->body =
|
||||
$options['body'] instanceof Body
|
||||
? $options['body']
|
||||
: new Body($options['body']);
|
||||
}
|
||||
|
||||
if (isset($options['files']) === true) {
|
||||
$this->files =
|
||||
$options['files'] instanceof Files
|
||||
? $options['files']
|
||||
: new Files($options['files']);
|
||||
}
|
||||
|
||||
if (isset($options['query']) === true) {
|
||||
$this->query =
|
||||
$options['query'] instanceof Query
|
||||
? $options['query']
|
||||
: new Query($options['query']);
|
||||
}
|
||||
|
||||
if (isset($options['url']) === true) {
|
||||
$this->url =
|
||||
$options['url'] instanceof Uri
|
||||
? $options['url']
|
||||
: new Uri($options['url']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Improved `var_dump` output
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
return [
|
||||
'body' => $this->body(),
|
||||
'files' => $this->files(),
|
||||
'method' => $this->method(),
|
||||
'query' => $this->query(),
|
||||
'url' => $this->url()->toString()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Auth object if authentication is set
|
||||
*/
|
||||
public function auth(): Auth|false|null
|
||||
{
|
||||
if ($this->auth !== null) {
|
||||
return $this->auth;
|
||||
}
|
||||
|
||||
// lazily request the instance for non-CMS use cases
|
||||
$kirby = App::instance(lazy: true);
|
||||
|
||||
// tell the CMS responder that the response relies on
|
||||
// the `Authorization` header and its value (even if
|
||||
// the header isn't set in the current request);
|
||||
// this ensures that the response is only cached for
|
||||
// unauthenticated visitors;
|
||||
// https://github.com/getkirby/kirby/issues/4423#issuecomment-1166300526
|
||||
$kirby?->response()->usesAuth(true);
|
||||
|
||||
if ($auth = $this->authString()) {
|
||||
$type = Str::lower(Str::before($auth, ' '));
|
||||
$data = Str::after($auth, ' ');
|
||||
|
||||
$class = static::$authTypes[$type] ?? null;
|
||||
if (!$class || class_exists($class) === false) {
|
||||
return $this->auth = false;
|
||||
}
|
||||
|
||||
$object = new $class($data);
|
||||
|
||||
return $this->auth = $object;
|
||||
}
|
||||
|
||||
return $this->auth = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Body object
|
||||
*/
|
||||
public function body(): Body
|
||||
{
|
||||
return $this->body ??= new Body();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the request has been made from the command line
|
||||
*/
|
||||
public function cli(): bool
|
||||
{
|
||||
return $this->options['cli'] ?? (new Environment())->cli();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a CSRF token if stored in a header or the query
|
||||
*/
|
||||
public function csrf(): string|null
|
||||
{
|
||||
return $this->header('x-csrf') ?? $this->query()->get('csrf');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the request input as array
|
||||
*/
|
||||
public function data(): array
|
||||
{
|
||||
return array_replace(
|
||||
$this->body()->toArray(),
|
||||
$this->query()->toArray()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the request method from various
|
||||
* options: given method, query string, server vars
|
||||
*/
|
||||
public function detectRequestMethod(string|null $method = null): string
|
||||
{
|
||||
// all possible methods
|
||||
$methods = [
|
||||
'CONNECT',
|
||||
'DELETE',
|
||||
'GET',
|
||||
'HEAD',
|
||||
'OPTIONS',
|
||||
'PATCH',
|
||||
'POST',
|
||||
'PUT',
|
||||
'TRACE',
|
||||
];
|
||||
|
||||
// the request method can be overwritten with a header
|
||||
if ($method === null) {
|
||||
$override = Environment::getGlobally('HTTP_X_HTTP_METHOD_OVERRIDE', '');
|
||||
$override = strtoupper($override);
|
||||
|
||||
if (in_array($override, $methods, true) === true) {
|
||||
$method = $override;
|
||||
}
|
||||
}
|
||||
|
||||
// final chain of options to detect the method
|
||||
$method ??= Environment::getGlobally('REQUEST_METHOD', 'GET');
|
||||
|
||||
// uppercase the shit out of it
|
||||
$method = strtoupper($method);
|
||||
|
||||
// sanitize the method
|
||||
if (in_array($method, $methods, true) === false) {
|
||||
$method = 'GET';
|
||||
}
|
||||
|
||||
return $method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the domain
|
||||
*/
|
||||
public function domain(): string
|
||||
{
|
||||
return $this->url()->domain();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a single file array
|
||||
* from the Files object by key
|
||||
*/
|
||||
public function file(string $key): array|null
|
||||
{
|
||||
return $this->files()->get($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Files object
|
||||
*/
|
||||
public function files(): Files
|
||||
{
|
||||
return $this->files ??= new Files();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns any data field from the request
|
||||
* if it exists
|
||||
*/
|
||||
public function get(string|array|null $key = null, $fallback = null)
|
||||
{
|
||||
return A::get($this->data(), $key, $fallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the request contains
|
||||
* the `Authorization` header
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public function hasAuth(): bool
|
||||
{
|
||||
return $this->authString() !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a header by key if it exists
|
||||
*/
|
||||
public function header(string $key, $fallback = null)
|
||||
{
|
||||
$headers = array_change_key_case($this->headers());
|
||||
return $headers[strtolower($key)] ?? $fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all headers with polyfill for
|
||||
* missing getallheaders function
|
||||
*/
|
||||
public function headers(): array
|
||||
{
|
||||
$headers = [];
|
||||
|
||||
foreach (Environment::getGlobally() as $key => $value) {
|
||||
if (
|
||||
str_starts_with($key, 'HTTP_') === false &&
|
||||
str_starts_with($key, 'REDIRECT_HTTP_') === false
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// remove HTTP_
|
||||
$key = str_replace(['REDIRECT_HTTP_', 'HTTP_'], '', $key);
|
||||
|
||||
// convert to lowercase
|
||||
$key = strtolower($key);
|
||||
|
||||
// replace _ with spaces
|
||||
$key = str_replace('_', ' ', $key);
|
||||
|
||||
// uppercase first char in each word
|
||||
$key = ucwords($key);
|
||||
|
||||
// convert spaces to dashes
|
||||
$key = str_replace(' ', '-', $key);
|
||||
|
||||
$headers[$key] = $value;
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given method name
|
||||
* matches the name of the request method.
|
||||
*/
|
||||
public function is(string $method): bool
|
||||
{
|
||||
return strtoupper($this->method) === strtoupper($method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the request method
|
||||
*/
|
||||
public function method(): string
|
||||
{
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut to the Params object
|
||||
*/
|
||||
public function params(): Params
|
||||
{
|
||||
return $this->url()->params();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut to the Path object
|
||||
*/
|
||||
public function path(): Path
|
||||
{
|
||||
return $this->url()->path();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Query object
|
||||
*/
|
||||
public function query(): Query
|
||||
{
|
||||
return $this->query ??= new Query();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for a valid SSL connection
|
||||
*/
|
||||
public function ssl(): bool
|
||||
{
|
||||
return $this->url()->scheme() === 'https';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current Uri object.
|
||||
* If you pass props you can safely modify
|
||||
* the Url with new parameters without destroying
|
||||
* the original object.
|
||||
*/
|
||||
public function url(array|null $props = null): Uri
|
||||
{
|
||||
if ($props !== null) {
|
||||
return $this->url()->clone($props);
|
||||
}
|
||||
|
||||
return $this->url ??= Uri::current();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw auth string from the `auth` option
|
||||
* or `Authorization` header unless both are empty
|
||||
*/
|
||||
protected function authString(): string|null
|
||||
{
|
||||
// both variants need to be checked separately
|
||||
// because empty strings are treated as invalid
|
||||
// but the `??` operator wouldn't do the fallback
|
||||
$option = $this->options['auth'] ?? null;
|
||||
|
||||
if (is_string($option) === true && $option !== '') {
|
||||
return $option;
|
||||
}
|
||||
|
||||
$header = $this->header('authorization');
|
||||
|
||||
if (is_string($header) === true && $header !== '') {
|
||||
return $header;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue