190 lines
5.6 KiB
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;
|
|
}
|
|
}
|