index/src/Http/HttpRequest.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;
}
}