239 lines
8 KiB
PHP
239 lines
8 KiB
PHP
<?php
|
|
// HttpRequest.php
|
|
// Created: 2022-02-08
|
|
// Updated: 2024-10-02
|
|
|
|
namespace Index\Http;
|
|
|
|
use InvalidArgumentException;
|
|
use RuntimeException;
|
|
use Index\MediaType;
|
|
use Index\Http\Content\{HttpContent,FormContent,JsonContent,StringContent};
|
|
|
|
/**
|
|
* Represents a HTTP request message.
|
|
*/
|
|
class HttpRequest extends HttpMessage {
|
|
/**
|
|
* @param string $version HTTP message version.
|
|
* @param string $method HTTP request method.
|
|
* @param string $path HTTP request path.
|
|
* @param array<string, mixed> $params HTTP request query parameters.
|
|
* @param array<string, string> $cookies HTTP request cookies.
|
|
* @param HttpHeaders $headers HTTP message headers.
|
|
* @param ?HttpContent $content Body contents.
|
|
*/
|
|
public function __construct(
|
|
string $version,
|
|
private string $method,
|
|
private string $path,
|
|
private array $params,
|
|
private array $cookies,
|
|
HttpHeaders $headers,
|
|
?HttpContent $content
|
|
) {
|
|
parent::__construct($version, $headers, $content);
|
|
}
|
|
|
|
/**
|
|
* Retrieves the HTTP request method.
|
|
*
|
|
* @return string HTTP request method.
|
|
*/
|
|
public function getMethod(): string {
|
|
return $this->method;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the HTTP request path.
|
|
*
|
|
* @return string HTTP request path.
|
|
*/
|
|
public function getPath(): string {
|
|
return $this->path;
|
|
}
|
|
|
|
/**
|
|
* Retrieves all HTTP request query fields as a query string.
|
|
*
|
|
* @param bool $spacesAsPlus true if spaces should be represented with a +, false if %20.
|
|
* @return string Query string representation of query fields.
|
|
*/
|
|
public function getParamString(bool $spacesAsPlus = false): string {
|
|
return http_build_query($this->params, '', '&', $spacesAsPlus ? PHP_QUERY_RFC1738 : PHP_QUERY_RFC3986);
|
|
}
|
|
|
|
/**
|
|
* Retrieves all HTTP request query fields.
|
|
*
|
|
* @return array<string, mixed> Query fields.
|
|
*/
|
|
public function getParams(): array {
|
|
return $this->params;
|
|
}
|
|
|
|
/**
|
|
* Retrieves an HTTP request query field, or null if it is not present.
|
|
*
|
|
* @param string $name Name of the request query field.
|
|
* @param int $filter A PHP filter extension filter constant.
|
|
* @param mixed[]|int $options Options for the PHP filter.
|
|
* @return mixed Value of the query field, null if not present.
|
|
*/
|
|
public function getParam(string $name, int $filter = FILTER_DEFAULT, array|int $options = 0): mixed {
|
|
if(!isset($this->params[$name]))
|
|
return null;
|
|
return filter_var($this->params[$name], $filter, $options);
|
|
}
|
|
|
|
/**
|
|
* Checks if a query field is present.
|
|
*
|
|
* @param string $name Name of the query field.
|
|
* @return bool true if the field is present, false if not.
|
|
*/
|
|
public function hasParam(string $name): bool {
|
|
return isset($this->params[$name]);
|
|
}
|
|
|
|
/**
|
|
* Retrieves all HTTP request cookies.
|
|
*
|
|
* @return array<string, string> All cookies.
|
|
*/
|
|
public function getCookies(): array {
|
|
return $this->cookies;
|
|
}
|
|
|
|
/**
|
|
* Retrieves an HTTP request cookie, or null if it is not present.
|
|
*
|
|
* @param string $name Name of the request cookie.
|
|
* @param int $filter A PHP filter extension filter constant.
|
|
* @param array<string, mixed>|int $options Options for the PHP filter.
|
|
* @return mixed Value of the cookie, null if not present.
|
|
*/
|
|
public function getCookie(string $name, int $filter = FILTER_DEFAULT, array|int $options = 0): mixed {
|
|
if(!isset($this->cookies[$name]))
|
|
return null;
|
|
return filter_var($this->cookies[$name], $filter, $options);
|
|
}
|
|
|
|
/**
|
|
* Checks if a cookie is present.
|
|
*
|
|
* @param string $name Name of the cookie.
|
|
* @return bool true if the cookie is present, false if not.
|
|
*/
|
|
public function hasCookie(string $name): bool {
|
|
return isset($this->cookies[$name]);
|
|
}
|
|
|
|
/**
|
|
* Creates an HttpRequest instance from the current request.
|
|
*
|
|
* @return HttpRequest An instance representing the current request.
|
|
*/
|
|
public static function fromRequest(): HttpRequest {
|
|
$build = new HttpRequestBuilder;
|
|
$build->setHttpVersion((string)filter_input(INPUT_SERVER, 'SERVER_PROTOCOL'));
|
|
$build->setMethod((string)filter_input(INPUT_SERVER, 'REQUEST_METHOD'));
|
|
|
|
// this currently doesn't "properly" support the scenario where a full url is specified in the http request
|
|
$path = (string)filter_input(INPUT_SERVER, 'REQUEST_URI');
|
|
$pathQueryOffset = strpos($path, '?');
|
|
if($pathQueryOffset !== false)
|
|
$path = substr($path, 0, $pathQueryOffset);
|
|
else {
|
|
$pathHashOffset = strpos($path, '#');
|
|
if($pathHashOffset !== false)
|
|
$path = substr($path, 0, $pathHashOffset);
|
|
}
|
|
|
|
if(!str_starts_with($path, '/'))
|
|
$path = '/' . $path;
|
|
|
|
$build->setPath($path);
|
|
|
|
/** @var array<string, mixed> $getVars */
|
|
$getVars = $_GET;
|
|
$build->setParams($getVars);
|
|
|
|
/** @var array<string, string> $cookieVars */
|
|
$cookieVars = $_COOKIE;
|
|
$build->setCookies($cookieVars);
|
|
|
|
$contentType = null;
|
|
$contentLength = 0;
|
|
|
|
$headers = self::getRawRequestHeaders();
|
|
foreach($headers as $name => $value) {
|
|
if($name === 'content-type')
|
|
try {
|
|
$contentType = MediaType::parse($value);
|
|
} catch(InvalidArgumentException $ex) {
|
|
$contentType = null;
|
|
}
|
|
elseif($name === 'content-length')
|
|
$contentLength = (int)$value;
|
|
|
|
$build->setHeader($name, $value);
|
|
}
|
|
|
|
if($contentType !== null
|
|
&& ($contentType->equals('application/json') || $contentType->equals('application/x-json')))
|
|
$build->setContent(JsonContent::fromRequest());
|
|
elseif($contentType !== null
|
|
&& ($contentType->equals('application/x-www-form-urlencoded') || $contentType->equals('multipart/form-data')))
|
|
$build->setContent(FormContent::fromRequest());
|
|
elseif($contentLength > 0)
|
|
$build->setContent(StringContent::fromRequest());
|
|
|
|
return $build->toRequest();
|
|
}
|
|
|
|
/** @return array<string, string> */
|
|
private static function getRawRequestHeaders(): array {
|
|
if(function_exists('getallheaders')) {
|
|
$raw = getallheaders();
|
|
$headers = [];
|
|
foreach($raw as $name => $value)
|
|
if(is_string($name) && is_string($value))
|
|
$headers[strtolower($name)] = $value;
|
|
|
|
return $headers;
|
|
}
|
|
|
|
$headers = [];
|
|
|
|
foreach($_SERVER as $key => $value) {
|
|
if(!is_string($key) || !is_scalar($value))
|
|
continue;
|
|
|
|
if(substr($key, 0, 5) === 'HTTP_') {
|
|
$key = str_replace(' ', '-', strtolower(str_replace('_', ' ', substr($key, 5))));
|
|
$headers[$key] = (string)$value;
|
|
} elseif($key === 'CONTENT_TYPE') {
|
|
$headers['content-type'] = (string)$value;
|
|
} elseif($key === 'CONTENT_LENGTH') {
|
|
$headers['content-length'] = (string)$value;
|
|
}
|
|
}
|
|
|
|
if(!isset($headers['authorization'])) {
|
|
if(filter_has_var(INPUT_SERVER, 'REDIRECT_HTTP_AUTHORIZATION')) {
|
|
$headers['authorization'] = (string)filter_input(INPUT_SERVER, 'REDIRECT_HTTP_AUTHORIZATION');
|
|
} elseif(filter_has_var(INPUT_SERVER, 'PHP_AUTH_USER')) {
|
|
$headers['authorization'] = sprintf('Basic %s', base64_encode(sprintf(
|
|
'%s:%s',
|
|
(string)filter_input(INPUT_SERVER, 'PHP_AUTH_USER'),
|
|
(string)filter_input(INPUT_SERVER, 'PHP_AUTH_PW')
|
|
)));
|
|
} elseif(filter_has_var(INPUT_SERVER, 'PHP_AUTH_DIGEST')) {
|
|
$headers['authorization'] = (string)filter_input(INPUT_SERVER, 'PHP_AUTH_DIGEST');
|
|
}
|
|
}
|
|
|
|
return $headers;
|
|
}
|
|
}
|