Initial Version
This commit is contained in:
2107
core/lib/Http/Request/Request.php
Normal file
2107
core/lib/Http/Request/Request.php
Normal file
File diff suppressed because it is too large
Load Diff
129
core/lib/Http/Request/RequestFileCollection.php
Normal file
129
core/lib/Http/Request/RequestFileCollection.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KTXC\Http\Request;
|
||||
|
||||
use KTXC\Http\File\UploadedFile;
|
||||
|
||||
/**
|
||||
* FileBag is a container for uploaded files.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*/
|
||||
class RequestFileCollection extends RequestParameters
|
||||
{
|
||||
private const FILE_KEYS = ['error', 'full_path', 'name', 'size', 'tmp_name', 'type'];
|
||||
|
||||
/**
|
||||
* @param array|UploadedFile[] $parameters An array of HTTP files
|
||||
*/
|
||||
public function __construct(array $parameters = [])
|
||||
{
|
||||
$this->replace($parameters);
|
||||
}
|
||||
|
||||
public function replace(array $files = []): void
|
||||
{
|
||||
$this->parameters = [];
|
||||
$this->add($files);
|
||||
}
|
||||
|
||||
public function set(string $key, mixed $value): void
|
||||
{
|
||||
if (!\is_array($value) && !$value instanceof UploadedFile) {
|
||||
throw new \InvalidArgumentException('An uploaded file must be an array or an instance of UploadedFile.');
|
||||
}
|
||||
|
||||
parent::set($key, $this->convertFileInformation($value));
|
||||
}
|
||||
|
||||
public function add(array $files = []): void
|
||||
{
|
||||
foreach ($files as $key => $file) {
|
||||
$this->set($key, $file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts uploaded files to UploadedFile instances.
|
||||
*
|
||||
* @return UploadedFile[]|UploadedFile|null
|
||||
*/
|
||||
protected function convertFileInformation(array|UploadedFile $file): array|UploadedFile|null
|
||||
{
|
||||
if ($file instanceof UploadedFile) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
$file = $this->fixPhpFilesArray($file);
|
||||
$keys = array_keys($file + ['full_path' => null]);
|
||||
sort($keys);
|
||||
|
||||
if (self::FILE_KEYS === $keys) {
|
||||
if (\UPLOAD_ERR_NO_FILE === $file['error']) {
|
||||
$file = null;
|
||||
} else {
|
||||
$file = new UploadedFile($file['tmp_name'], $file['full_path'] ?? $file['name'], $file['type'], $file['error'], false);
|
||||
}
|
||||
} else {
|
||||
$file = array_map(fn ($v) => $v instanceof UploadedFile || \is_array($v) ? $this->convertFileInformation($v) : $v, $file);
|
||||
if (array_is_list($file)) {
|
||||
$file = array_filter($file);
|
||||
}
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes a malformed PHP $_FILES array.
|
||||
*
|
||||
* PHP has a bug that the format of the $_FILES array differs, depending on
|
||||
* whether the uploaded file fields had normal field names or array-like
|
||||
* field names ("normal" vs. "parent[child]").
|
||||
*
|
||||
* This method fixes the array to look like the "normal" $_FILES array.
|
||||
*
|
||||
* It's safe to pass an already converted array, in which case this method
|
||||
* just returns the original array unmodified.
|
||||
*/
|
||||
protected function fixPhpFilesArray(array $data): array
|
||||
{
|
||||
$keys = array_keys($data + ['full_path' => null]);
|
||||
sort($keys);
|
||||
|
||||
if (self::FILE_KEYS !== $keys || !isset($data['name']) || !\is_array($data['name'])) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$files = $data;
|
||||
foreach (self::FILE_KEYS as $k) {
|
||||
unset($files[$k]);
|
||||
}
|
||||
|
||||
foreach ($data['name'] as $key => $name) {
|
||||
$files[$key] = $this->fixPhpFilesArray([
|
||||
'error' => $data['error'][$key],
|
||||
'name' => $name,
|
||||
'type' => $data['type'][$key],
|
||||
'tmp_name' => $data['tmp_name'][$key],
|
||||
'size' => $data['size'][$key],
|
||||
] + (isset($data['full_path'][$key]) ? [
|
||||
'full_path' => $data['full_path'][$key],
|
||||
] : []));
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
}
|
||||
154
core/lib/Http/Request/RequestHeaderAccept.php
Normal file
154
core/lib/Http/Request/RequestHeaderAccept.php
Normal file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KTXC\Http\Request;
|
||||
|
||||
use KTXC\Http\HeaderUtils;
|
||||
|
||||
// Help opcache.preload discover always-needed symbols
|
||||
class_exists(RequestHeaderAcceptItem::class);
|
||||
|
||||
/**
|
||||
* Represents an Accept-* header.
|
||||
*
|
||||
* An accept header is compound with a list of items,
|
||||
* sorted by descending quality.
|
||||
*
|
||||
* @author Jean-François Simon <contact@jfsimon.fr>
|
||||
*/
|
||||
class RequestHeaderAccept
|
||||
{
|
||||
/**
|
||||
* @var RequestHeaderAcceptItem[]
|
||||
*/
|
||||
private array $items = [];
|
||||
|
||||
private bool $sorted = true;
|
||||
|
||||
/**
|
||||
* @param RequestHeaderAcceptItem[] $items
|
||||
*/
|
||||
public function __construct(array $items)
|
||||
{
|
||||
foreach ($items as $item) {
|
||||
$this->add($item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an AcceptHeader instance from a string.
|
||||
*/
|
||||
public static function fromString(?string $headerValue): self
|
||||
{
|
||||
$parts = HeaderUtils::split($headerValue ?? '', ',;=');
|
||||
|
||||
return new self(array_map(function ($subParts) {
|
||||
static $index = 0;
|
||||
$part = array_shift($subParts);
|
||||
$attributes = HeaderUtils::combine($subParts);
|
||||
|
||||
$item = new RequestHeaderAcceptItem($part[0], $attributes);
|
||||
$item->setIndex($index++);
|
||||
|
||||
return $item;
|
||||
}, $parts));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns header value's string representation.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return implode(',', $this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if header has given value.
|
||||
*/
|
||||
public function has(string $value): bool
|
||||
{
|
||||
return isset($this->items[$value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns given value's item, if exists.
|
||||
*/
|
||||
public function get(string $value): ?RequestHeaderAcceptItem
|
||||
{
|
||||
return $this->items[$value] ?? $this->items[explode('/', $value)[0].'/*'] ?? $this->items['*/*'] ?? $this->items['*'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an item.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function add(RequestHeaderAcceptItem $item): static
|
||||
{
|
||||
$this->items[$item->getValue()] = $item;
|
||||
$this->sorted = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all items.
|
||||
*
|
||||
* @return RequestHeaderAcceptItem[]
|
||||
*/
|
||||
public function all(): array
|
||||
{
|
||||
$this->sort();
|
||||
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters items on their value using given regex.
|
||||
*/
|
||||
public function filter(string $pattern): self
|
||||
{
|
||||
return new self(array_filter($this->items, fn (RequestHeaderAcceptItem $item) => preg_match($pattern, $item->getValue())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns first item.
|
||||
*/
|
||||
public function first(): ?RequestHeaderAcceptItem
|
||||
{
|
||||
$this->sort();
|
||||
|
||||
return $this->items ? reset($this->items) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts items by descending quality.
|
||||
*/
|
||||
private function sort(): void
|
||||
{
|
||||
if (!$this->sorted) {
|
||||
uasort($this->items, function (RequestHeaderAcceptItem $a, RequestHeaderAcceptItem $b) {
|
||||
$qA = $a->getQuality();
|
||||
$qB = $b->getQuality();
|
||||
|
||||
if ($qA === $qB) {
|
||||
return $a->getIndex() > $b->getIndex() ? 1 : -1;
|
||||
}
|
||||
|
||||
return $qA > $qB ? -1 : 1;
|
||||
});
|
||||
|
||||
$this->sorted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
163
core/lib/Http/Request/RequestHeaderAcceptItem.php
Normal file
163
core/lib/Http/Request/RequestHeaderAcceptItem.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KTXC\Http\Request;
|
||||
|
||||
use KTXC\Http\HeaderUtils;
|
||||
|
||||
/**
|
||||
* Represents an Accept-* header item.
|
||||
*
|
||||
* @author Jean-François Simon <contact@jfsimon.fr>
|
||||
*/
|
||||
class RequestHeaderAcceptItem
|
||||
{
|
||||
private float $quality = 1.0;
|
||||
private int $index = 0;
|
||||
private array $attributes = [];
|
||||
|
||||
public function __construct(
|
||||
private string $value,
|
||||
array $attributes = [],
|
||||
) {
|
||||
foreach ($attributes as $name => $value) {
|
||||
$this->setAttribute($name, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an AcceptHeaderInstance instance from a string.
|
||||
*/
|
||||
public static function fromString(?string $itemValue): self
|
||||
{
|
||||
$parts = HeaderUtils::split($itemValue ?? '', ';=');
|
||||
|
||||
$part = array_shift($parts);
|
||||
$attributes = HeaderUtils::combine($parts);
|
||||
|
||||
return new self($part[0], $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns header value's string representation.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
$string = $this->value.($this->quality < 1 ? ';q='.$this->quality : '');
|
||||
if (\count($this->attributes) > 0) {
|
||||
$string .= '; '.HeaderUtils::toString($this->attributes, ';');
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the item value.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setValue(string $value): static
|
||||
{
|
||||
$this->value = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item value.
|
||||
*/
|
||||
public function getValue(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the item quality.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setQuality(float $quality): static
|
||||
{
|
||||
$this->quality = $quality;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item quality.
|
||||
*/
|
||||
public function getQuality(): float
|
||||
{
|
||||
return $this->quality;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the item index.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setIndex(int $index): static
|
||||
{
|
||||
$this->index = $index;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item index.
|
||||
*/
|
||||
public function getIndex(): int
|
||||
{
|
||||
return $this->index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if an attribute exists.
|
||||
*/
|
||||
public function hasAttribute(string $name): bool
|
||||
{
|
||||
return isset($this->attributes[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an attribute by its name.
|
||||
*/
|
||||
public function getAttribute(string $name, mixed $default = null): mixed
|
||||
{
|
||||
return $this->attributes[$name] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all attributes.
|
||||
*/
|
||||
public function getAttributes(): array
|
||||
{
|
||||
return $this->attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an attribute.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAttribute(string $name, string $value): static
|
||||
{
|
||||
if ('q' === $name) {
|
||||
$this->quality = (float) $value;
|
||||
} else {
|
||||
$this->attributes[$name] = $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
12
core/lib/Http/Request/RequestHeaderParameters.php
Normal file
12
core/lib/Http/Request/RequestHeaderParameters.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace KTXC\Http\Request;
|
||||
|
||||
use KTXC\Http\HeaderParameters;
|
||||
|
||||
/**
|
||||
* HeaderBag is a container for HTTP headers.
|
||||
*/
|
||||
class RequestHeaderParameters extends HeaderParameters {}
|
||||
155
core/lib/Http/Request/RequestInputParameters.php
Normal file
155
core/lib/Http/Request/RequestInputParameters.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KTXC\Http\Request;
|
||||
|
||||
use KTXC\Http\Exception\BadRequestException;
|
||||
use KTXC\Http\Exception\UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* InputBag is a container for user input values such as $_GET, $_POST, $_REQUEST, and $_COOKIE.
|
||||
*
|
||||
* @author Saif Eddin Gmati <azjezz@protonmail.com>
|
||||
*/
|
||||
final class RequestInputParameters extends RequestParameters
|
||||
{
|
||||
/**
|
||||
* Returns an input value by name (scalar, Stringable, or array).
|
||||
*
|
||||
* Arrays are now allowed. (Previously only scalar values were permitted.)
|
||||
* No deep validation of array contents is performed here; callers should
|
||||
* sanitize nested values as needed.
|
||||
*
|
||||
* @param string|int|float|bool|array|null $default The default value if the key does not exist
|
||||
*
|
||||
* @return string|int|float|bool|array|null
|
||||
*
|
||||
* @throws BadRequestException if the stored input value is of an unsupported type
|
||||
* @throws \InvalidArgumentException if the provided default is of an unsupported type
|
||||
*/
|
||||
public function get(string $key, mixed $default = null): string|int|float|bool|array|null
|
||||
{
|
||||
if (null !== $default && !\is_scalar($default) && !$default instanceof \Stringable && !\is_array($default)) {
|
||||
throw new \InvalidArgumentException(\sprintf('Expected a scalar or array value as a 2nd argument to "%s()", "%s" given.', __METHOD__, get_debug_type($default)));
|
||||
}
|
||||
|
||||
$value = parent::get($key, $this);
|
||||
|
||||
if (null !== $value && $this !== $value && !\is_scalar($value) && !$value instanceof \Stringable && !\is_array($value)) {
|
||||
throw new BadRequestException(\sprintf('Input value "%s" contains an invalid (non-scalar, non-array, non-Stringable) value.', $key));
|
||||
}
|
||||
|
||||
return $this === $value ? $default : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the current input values by a new set.
|
||||
*/
|
||||
public function replace(array $inputs = []): void
|
||||
{
|
||||
$this->parameters = [];
|
||||
$this->add($inputs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds input values.
|
||||
*/
|
||||
public function add(array $inputs = []): void
|
||||
{
|
||||
foreach ($inputs as $input => $value) {
|
||||
$this->set($input, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an input by name.
|
||||
*
|
||||
* @param string|int|float|bool|array|null $value
|
||||
*/
|
||||
public function set(string $key, mixed $value): void
|
||||
{
|
||||
if (null !== $value && !\is_scalar($value) && !\is_array($value) && !$value instanceof \Stringable) {
|
||||
throw new \InvalidArgumentException(\sprintf('Expected a scalar, or an array as a 2nd argument to "%s()", "%s" given.', __METHOD__, get_debug_type($value)));
|
||||
}
|
||||
|
||||
$this->parameters[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parameter value converted to an enum.
|
||||
*
|
||||
* @template T of \BackedEnum
|
||||
*
|
||||
* @param class-string<T> $class
|
||||
* @param ?T $default
|
||||
*
|
||||
* @return ?T
|
||||
*
|
||||
* @psalm-return ($default is null ? T|null : T)
|
||||
*
|
||||
* @throws BadRequestException if the input cannot be converted to an enum
|
||||
*/
|
||||
public function getEnum(string $key, string $class, ?\BackedEnum $default = null): ?\BackedEnum
|
||||
{
|
||||
try {
|
||||
return parent::getEnum($key, $class, $default);
|
||||
} catch (UnexpectedValueException $e) {
|
||||
throw new BadRequestException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parameter value converted to string.
|
||||
*
|
||||
* @throws BadRequestException if the input contains a non-scalar value
|
||||
*/
|
||||
public function getString(string $key, string $default = ''): string
|
||||
{
|
||||
// Shortcuts the parent method because the validation on scalar is already done in get().
|
||||
return (string) $this->get($key, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequestException if the input value is an array and \FILTER_REQUIRE_ARRAY or \FILTER_FORCE_ARRAY is not set
|
||||
* @throws BadRequestException if the input value is invalid and \FILTER_NULL_ON_FAILURE is not set
|
||||
*/
|
||||
public function filter(string $key, mixed $default = null, int $filter = \FILTER_DEFAULT, mixed $options = []): mixed
|
||||
{
|
||||
$value = $this->has($key) ? $this->all()[$key] : $default;
|
||||
|
||||
// Always turn $options into an array - this allows filter_var option shortcuts.
|
||||
if (!\is_array($options) && $options) {
|
||||
$options = ['flags' => $options];
|
||||
}
|
||||
|
||||
if (\is_array($value) && !(($options['flags'] ?? 0) & (\FILTER_REQUIRE_ARRAY | \FILTER_FORCE_ARRAY))) {
|
||||
throw new BadRequestException(\sprintf('Input value "%s" contains an array, but "FILTER_REQUIRE_ARRAY" or "FILTER_FORCE_ARRAY" flags were not set.', $key));
|
||||
}
|
||||
|
||||
if ((\FILTER_CALLBACK & $filter) && !(($options['options'] ?? null) instanceof \Closure)) {
|
||||
throw new \InvalidArgumentException(\sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null)));
|
||||
}
|
||||
|
||||
$options['flags'] ??= 0;
|
||||
$nullOnFailure = $options['flags'] & \FILTER_NULL_ON_FAILURE;
|
||||
$options['flags'] |= \FILTER_NULL_ON_FAILURE;
|
||||
|
||||
$value = filter_var($value, $filter, $options);
|
||||
|
||||
if (null !== $value || $nullOnFailure) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
throw new BadRequestException(\sprintf('Input value "%s" is invalid and flag "FILTER_NULL_ON_FAILURE" was not set.', $key));
|
||||
}
|
||||
}
|
||||
260
core/lib/Http/Request/RequestParameters.php
Normal file
260
core/lib/Http/Request/RequestParameters.php
Normal file
@@ -0,0 +1,260 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KTXC\Http\Request;
|
||||
|
||||
use KTXC\Http\Exception\BadRequestException;
|
||||
use KTXC\Http\Exception\UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* ParameterBag is a container for key/value pairs.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @implements \IteratorAggregate<string, mixed>
|
||||
*/
|
||||
class RequestParameters implements \IteratorAggregate, \Countable
|
||||
{
|
||||
public function __construct(
|
||||
protected array $parameters = [],
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parameters.
|
||||
*
|
||||
* @param string|null $key The name of the parameter to return or null to get them all
|
||||
*
|
||||
* @throws BadRequestException if the value is not an array
|
||||
*/
|
||||
public function all(?string $key = null): array
|
||||
{
|
||||
if (null === $key) {
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
if (!\is_array($value = $this->parameters[$key] ?? [])) {
|
||||
throw new BadRequestException(\sprintf('Unexpected value for parameter "%s": expecting "array", got "%s".', $key, get_debug_type($value)));
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parameter keys.
|
||||
*/
|
||||
public function keys(): array
|
||||
{
|
||||
return array_keys($this->parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the current parameters by a new set.
|
||||
*/
|
||||
public function replace(array $parameters = []): void
|
||||
{
|
||||
$this->parameters = $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds parameters.
|
||||
*/
|
||||
public function add(array $parameters = []): void
|
||||
{
|
||||
$this->parameters = array_replace($this->parameters, $parameters);
|
||||
}
|
||||
|
||||
public function get(string $key, mixed $default = null): mixed
|
||||
{
|
||||
return \array_key_exists($key, $this->parameters) ? $this->parameters[$key] : $default;
|
||||
}
|
||||
|
||||
public function set(string $key, mixed $value): void
|
||||
{
|
||||
$this->parameters[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the parameter is defined.
|
||||
*/
|
||||
public function has(string $key): bool
|
||||
{
|
||||
return \array_key_exists($key, $this->parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a parameter.
|
||||
*/
|
||||
public function remove(string $key): void
|
||||
{
|
||||
unset($this->parameters[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the alphabetic characters of the parameter value.
|
||||
*
|
||||
* @throws UnexpectedValueException if the value cannot be converted to string
|
||||
*/
|
||||
public function getAlpha(string $key, string $default = ''): string
|
||||
{
|
||||
return preg_replace('/[^[:alpha:]]/', '', $this->getString($key, $default));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the alphabetic characters and digits of the parameter value.
|
||||
*
|
||||
* @throws UnexpectedValueException if the value cannot be converted to string
|
||||
*/
|
||||
public function getAlnum(string $key, string $default = ''): string
|
||||
{
|
||||
return preg_replace('/[^[:alnum:]]/', '', $this->getString($key, $default));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the digits of the parameter value.
|
||||
*
|
||||
* @throws UnexpectedValueException if the value cannot be converted to string
|
||||
*/
|
||||
public function getDigits(string $key, string $default = ''): string
|
||||
{
|
||||
return preg_replace('/[^[:digit:]]/', '', $this->getString($key, $default));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parameter as string.
|
||||
*
|
||||
* @throws UnexpectedValueException if the value cannot be converted to string
|
||||
*/
|
||||
public function getString(string $key, string $default = ''): string
|
||||
{
|
||||
$value = $this->get($key, $default);
|
||||
if (!\is_scalar($value) && !$value instanceof \Stringable) {
|
||||
throw new UnexpectedValueException(\sprintf('Parameter value "%s" cannot be converted to "string".', $key));
|
||||
}
|
||||
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parameter value converted to integer.
|
||||
*
|
||||
* @throws UnexpectedValueException if the value cannot be converted to integer
|
||||
*/
|
||||
public function getInt(string $key, int $default = 0): int
|
||||
{
|
||||
return $this->filter($key, $default, \FILTER_VALIDATE_INT, ['flags' => \FILTER_REQUIRE_SCALAR]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parameter value converted to boolean.
|
||||
*
|
||||
* @throws UnexpectedValueException if the value cannot be converted to a boolean
|
||||
*/
|
||||
public function getBoolean(string $key, bool $default = false): bool
|
||||
{
|
||||
return $this->filter($key, $default, \FILTER_VALIDATE_BOOL, ['flags' => \FILTER_REQUIRE_SCALAR]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parameter value converted to an enum.
|
||||
*
|
||||
* @template T of \BackedEnum
|
||||
*
|
||||
* @param class-string<T> $class
|
||||
* @param ?T $default
|
||||
*
|
||||
* @return ?T
|
||||
*
|
||||
* @psalm-return ($default is null ? T|null : T)
|
||||
*
|
||||
* @throws UnexpectedValueException if the parameter value cannot be converted to an enum
|
||||
*/
|
||||
public function getEnum(string $key, string $class, ?\BackedEnum $default = null): ?\BackedEnum
|
||||
{
|
||||
$value = $this->get($key);
|
||||
|
||||
if (null === $value) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
try {
|
||||
return $class::from($value);
|
||||
} catch (\ValueError|\TypeError $e) {
|
||||
throw new UnexpectedValueException(\sprintf('Parameter "%s" cannot be converted to enum: %s.', $key, $e->getMessage()), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter key.
|
||||
*
|
||||
* @param int $filter FILTER_* constant
|
||||
* @param int|array{flags?: int, options?: array} $options Flags from FILTER_* constants
|
||||
*
|
||||
* @see https://php.net/filter-var
|
||||
*
|
||||
* @throws UnexpectedValueException if the parameter value is a non-stringable object
|
||||
* @throws UnexpectedValueException if the parameter value is invalid and \FILTER_NULL_ON_FAILURE is not set
|
||||
*/
|
||||
public function filter(string $key, mixed $default = null, int $filter = \FILTER_DEFAULT, mixed $options = []): mixed
|
||||
{
|
||||
$value = $this->get($key, $default);
|
||||
|
||||
// Always turn $options into an array - this allows filter_var option shortcuts.
|
||||
if (!\is_array($options) && $options) {
|
||||
$options = ['flags' => $options];
|
||||
}
|
||||
|
||||
// Add a convenience check for arrays.
|
||||
if (\is_array($value) && !isset($options['flags'])) {
|
||||
$options['flags'] = \FILTER_REQUIRE_ARRAY;
|
||||
}
|
||||
|
||||
if (\is_object($value) && !$value instanceof \Stringable) {
|
||||
throw new UnexpectedValueException(\sprintf('Parameter value "%s" cannot be filtered.', $key));
|
||||
}
|
||||
|
||||
if ((\FILTER_CALLBACK & $filter) && !(($options['options'] ?? null) instanceof \Closure)) {
|
||||
throw new \InvalidArgumentException(\sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null)));
|
||||
}
|
||||
|
||||
$options['flags'] ??= 0;
|
||||
$nullOnFailure = $options['flags'] & \FILTER_NULL_ON_FAILURE;
|
||||
$options['flags'] |= \FILTER_NULL_ON_FAILURE;
|
||||
|
||||
$value = filter_var($value, $filter, $options);
|
||||
|
||||
if (null !== $value || $nullOnFailure) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
throw new \UnexpectedValueException(\sprintf('Parameter value "%s" is invalid and flag "FILTER_NULL_ON_FAILURE" was not set.', $key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator for parameters.
|
||||
*
|
||||
* @return \ArrayIterator<string, mixed>
|
||||
*/
|
||||
public function getIterator(): \ArrayIterator
|
||||
{
|
||||
return new \ArrayIterator($this->parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of parameters.
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return \count($this->parameters);
|
||||
}
|
||||
}
|
||||
99
core/lib/Http/Request/RequestServerParameters.php
Normal file
99
core/lib/Http/Request/RequestServerParameters.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KTXC\Http\Request;
|
||||
|
||||
/**
|
||||
* ServerBag is a container for HTTP headers from the $_SERVER variable.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
* @author Robert Kiss <kepten@gmail.com>
|
||||
*/
|
||||
class RequestServerParameters extends RequestParameters
|
||||
{
|
||||
/**
|
||||
* Gets the HTTP headers.
|
||||
*/
|
||||
public function getHeaders(): array
|
||||
{
|
||||
$headers = [];
|
||||
foreach ($this->parameters as $key => $value) {
|
||||
if (str_starts_with($key, 'HTTP_')) {
|
||||
$headers[substr($key, 5)] = $value;
|
||||
} elseif (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'], true) && '' !== $value) {
|
||||
$headers[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->parameters['PHP_AUTH_USER'])) {
|
||||
$headers['PHP_AUTH_USER'] = $this->parameters['PHP_AUTH_USER'];
|
||||
$headers['PHP_AUTH_PW'] = $this->parameters['PHP_AUTH_PW'] ?? '';
|
||||
} else {
|
||||
/*
|
||||
* php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default
|
||||
* For this workaround to work, add these lines to your .htaccess file:
|
||||
* RewriteCond %{HTTP:Authorization} .+
|
||||
* RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0]
|
||||
*
|
||||
* A sample .htaccess file:
|
||||
* RewriteEngine On
|
||||
* RewriteCond %{HTTP:Authorization} .+
|
||||
* RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0]
|
||||
* RewriteCond %{REQUEST_FILENAME} !-f
|
||||
* RewriteRule ^(.*)$ index.php [QSA,L]
|
||||
*/
|
||||
|
||||
$authorizationHeader = null;
|
||||
if (isset($this->parameters['HTTP_AUTHORIZATION'])) {
|
||||
$authorizationHeader = $this->parameters['HTTP_AUTHORIZATION'];
|
||||
} elseif (isset($this->parameters['REDIRECT_HTTP_AUTHORIZATION'])) {
|
||||
$authorizationHeader = $this->parameters['REDIRECT_HTTP_AUTHORIZATION'];
|
||||
}
|
||||
|
||||
if (null !== $authorizationHeader) {
|
||||
if (0 === stripos($authorizationHeader, 'basic ')) {
|
||||
// Decode AUTHORIZATION header into PHP_AUTH_USER and PHP_AUTH_PW when authorization header is basic
|
||||
$exploded = explode(':', base64_decode(substr($authorizationHeader, 6)), 2);
|
||||
if (2 == \count($exploded)) {
|
||||
[$headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']] = $exploded;
|
||||
}
|
||||
} elseif (empty($this->parameters['PHP_AUTH_DIGEST']) && (0 === stripos($authorizationHeader, 'digest '))) {
|
||||
// In some circumstances PHP_AUTH_DIGEST needs to be set
|
||||
$headers['PHP_AUTH_DIGEST'] = $authorizationHeader;
|
||||
$this->parameters['PHP_AUTH_DIGEST'] = $authorizationHeader;
|
||||
} elseif (0 === stripos($authorizationHeader, 'bearer ')) {
|
||||
/*
|
||||
* XXX: Since there is no PHP_AUTH_BEARER in PHP predefined variables,
|
||||
* I'll just set $headers['AUTHORIZATION'] here.
|
||||
* https://php.net/reserved.variables.server
|
||||
*/
|
||||
$headers['AUTHORIZATION'] = $authorizationHeader;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($headers['AUTHORIZATION'])) {
|
||||
return $headers;
|
||||
}
|
||||
|
||||
// PHP_AUTH_USER/PHP_AUTH_PW
|
||||
if (isset($headers['PHP_AUTH_USER'])) {
|
||||
$headers['AUTHORIZATION'] = 'Basic '.base64_encode($headers['PHP_AUTH_USER'].':'.($headers['PHP_AUTH_PW'] ?? ''));
|
||||
} elseif (isset($headers['PHP_AUTH_DIGEST'])) {
|
||||
$headers['AUTHORIZATION'] = $headers['PHP_AUTH_DIGEST'];
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user