Contractless-PHP-API/src/Http/Request.php

190 lines
5.6 KiB
PHP

<?php
declare(strict_types=1);
namespace Contractless\Api\Http;
final class Request
{
private static int $maximumBodyBytes = 2_000_000;
private static ?string $rawBody = null;
public static function setMaximumBodyBytes(int $bytes): void
{
self::$maximumBodyBytes = $bytes;
}
public static function rawBody(): string
{
if (self::$rawBody !== null) {
return self::$rawBody;
}
$raw = file_get_contents('php://input');
if ($raw === false) {
throw new HttpException(400, 'The request body could not be read.');
}
if (strlen($raw) > self::$maximumBodyBytes) {
throw new HttpException(413, 'The request body is too large.');
}
self::$rawBody = $raw;
return $raw;
}
public static function queryString(string $name, int $maxBytes = 4096): string
{
$value = $_GET[$name] ?? null;
if (!is_string($value)) {
throw new HttpException(422, "Query parameter $name is required.");
}
return self::validatedString($value, $name, $maxBytes);
}
public static function queryInt(
string $name,
int $default,
int $minimum,
int $maximum,
): int {
$value = $_GET[$name] ?? null;
if ($value === null || $value === '') {
return $default;
}
if (
!is_string($value)
|| filter_var($value, FILTER_VALIDATE_INT) === false
) {
throw new HttpException(422, "Query parameter $name must be an integer.");
}
$integer = (int) $value;
if ($integer < $minimum || $integer > $maximum) {
throw new HttpException(
422,
"Query parameter $name must be between $minimum and $maximum.",
);
}
return $integer;
}
public static function requiredQueryInt(
string $name,
int $minimum,
int $maximum,
): int {
$value = $_GET[$name] ?? null;
if (!is_string($value) || $value === '') {
throw new HttpException(422, "Query parameter $name is required.");
}
if (filter_var($value, FILTER_VALIDATE_INT) === false) {
throw new HttpException(422, "Query parameter $name must be an integer.");
}
$integer = (int) $value;
if ($integer < $minimum || $integer > $maximum) {
throw new HttpException(
422,
"Query parameter $name must be between $minimum and $maximum.",
);
}
return $integer;
}
public static function canonicalAddress(string $address): string
{
$address = strtolower(trim($address));
if (preg_match('/^[a-f0-9]{40}\.(clc|cltc)$/', $address) !== 1) {
throw new HttpException(422, 'Enter a valid Contractless wallet address.');
}
return $address;
}
public static function hash(string $hash, string $name): string
{
$hash = strtolower(trim($hash));
if (preg_match('/^[a-f0-9]{64}$/', $hash) !== 1) {
throw new HttpException(422, "Field $name must be a 64-character hash.");
}
return $hash;
}
public static function signature(string $signature): string
{
$signature = strtolower(trim($signature));
if (preg_match('/^[a-f0-9]{1332}$/', $signature) !== 1) {
throw new HttpException(
422,
'The signature must contain 1,332 hexadecimal characters.',
);
}
return $signature;
}
public static function transactionHex(string $transaction): string
{
$transaction = strtolower(trim($transaction));
if (
$transaction === ''
|| (strlen($transaction) % 2) !== 0
|| !ctype_xdigit($transaction)
) {
throw new HttpException(
422,
'The transaction must be complete hexadecimal data.',
);
}
return $transaction;
}
/** @return array<string, mixed> */
public static function json(): array
{
$contentType = strtolower(trim(explode(';', $_SERVER['CONTENT_TYPE'] ?? '')[0]));
if ($contentType !== 'application/json') {
throw new HttpException(415, 'Content-Type must be application/json.');
}
$raw = self::rawBody();
try {
$body = json_decode($raw, true, flags: JSON_THROW_ON_ERROR);
} catch (\JsonException) {
throw new HttpException(400, 'The request body is not valid JSON.');
}
if (!is_array($body)) {
throw new HttpException(400, 'The request body must be a JSON object.');
}
return $body;
}
/** @param array<string, mixed> $body */
public static function bodyString(
array $body,
string $name,
int $maxBytes = 4096,
bool $trim = true,
): string {
$value = $body[$name] ?? null;
if (!is_string($value)) {
throw new HttpException(422, "JSON field $name is required.");
}
return self::validatedString($value, $name, $maxBytes, $trim);
}
private static function validatedString(
string $value,
string $name,
int $maxBytes,
bool $trim = true,
): string {
if ($trim) {
$value = trim($value);
}
if ($value === '') {
throw new HttpException(422, "Field $name cannot be empty.");
}
if (strlen($value) > $maxBytes) {
throw new HttpException(422, "Field $name is too long.");
}
return $value;
}
}