This commit is contained in:
cutemeli
2025-12-22 10:35:30 +00:00
parent 0bfc6c8425
commit 5ce7ca2c5d
38927 changed files with 0 additions and 4594700 deletions

View File

@@ -1,30 +0,0 @@
<?php
declare(strict_types=1);
namespace GraphQL\Upload;
use Exception;
use GraphQL\Error\Error;
final class UploadError extends Error
{
public function __construct(int $uploadError)
{
parent::__construct('File upload: ' . $this->getMessageFromUploadError($uploadError));
}
private function getMessageFromUploadError(int $uploadError): string
{
return match ($uploadError) {
UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk',
UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the upload',
UPLOAD_ERR_FORM_SIZE => 'The file exceeds the `MAX_FILE_SIZE` directive that was specified in the HTML form',
UPLOAD_ERR_INI_SIZE => 'The file exceeds the `upload_max_filesize` of ' . Utility::toMebibyte(Utility::getUploadMaxFilesize()),
UPLOAD_ERR_NO_FILE => 'No file was uploaded',
UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder',
UPLOAD_ERR_PARTIAL => 'The file was only partially uploaded',
default => throw new Exception('Unsupported UPLOAD_ERR_* constant value: ' . $uploadError),
};
}
}

View File

@@ -1,133 +0,0 @@
<?php
declare(strict_types=1);
namespace GraphQL\Upload;
use GraphQL\Error\InvariantViolation;
use GraphQL\Server\RequestError;
use GraphQL\Utils\Utils;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class UploadMiddleware implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$contentType = $request->getHeader('content-type')[0] ?? '';
if (mb_stripos($contentType, 'multipart/form-data') !== false) {
$error = $this->postMaxSizeError($request);
if ($error) {
return $error;
}
$this->validateParsedBody($request);
$request = $this->parseUploadedFiles($request);
}
return $handler->handle($request);
}
/**
* Inject uploaded files defined in the 'map' key into the 'variables' key.
*/
private function parseUploadedFiles(ServerRequestInterface $request): ServerRequestInterface
{
/** @var string[] $bodyParams */
$bodyParams = $request->getParsedBody();
$map = $this->decodeArray($bodyParams, 'map');
$result = $this->decodeArray($bodyParams, 'operations');
$uploadedFiles = $request->getUploadedFiles();
foreach ($map as $fileKey => $locations) {
foreach ($locations as $location) {
$items = &$result;
foreach (explode('.', $location) as $key) {
if (!isset($items[$key]) || !is_array($items[$key])) {
$items[$key] = [];
}
$items = &$items[$key];
}
if (!array_key_exists($fileKey, $uploadedFiles)) {
throw new RequestError(
"GraphQL query declared an upload in `$location`, but no corresponding file were actually uploaded",
);
}
$items = $uploadedFiles[$fileKey];
}
}
return $request
->withHeader('content-type', 'application/json')
->withParsedBody($result);
}
/**
* Validates that the request meet our expectations.
*/
private function validateParsedBody(ServerRequestInterface $request): void
{
$bodyParams = $request->getParsedBody();
if (null === $bodyParams) {
throw new InvariantViolation(
'PSR-7 request is expected to provide parsed body for "multipart/form-data" requests but got null',
);
}
if (!is_array($bodyParams)) {
throw new RequestError(
'GraphQL Server expects JSON object or array, but got ' . Utils::printSafeJson($bodyParams),
);
}
if (empty($bodyParams)) {
throw new InvariantViolation(
'PSR-7 request is expected to provide parsed body for "multipart/form-data" requests but got empty array',
);
}
}
/**
* @param string[] $bodyParams
*
* @return string[][]
*/
private function decodeArray(array $bodyParams, string $key): array
{
if (!isset($bodyParams[$key])) {
throw new RequestError("The request must define a `$key`");
}
$value = json_decode($bodyParams[$key], true);
if (!is_array($value)) {
throw new RequestError("The `$key` key must be a JSON encoded array");
}
return $value;
}
private function postMaxSizeError(ServerRequestInterface $request): ?ResponseInterface
{
$contentLength = $request->getServerParams()['CONTENT_LENGTH'] ?? 0;
$postMaxSize = Utility::getPostMaxSize();
if ($contentLength && $contentLength > $postMaxSize) {
$contentLength = Utility::toMebibyte($contentLength);
$postMaxSize = Utility::toMebibyte($postMaxSize);
return new JsonResponse(
['message' => "The server `post_max_size` is configured to accept $postMaxSize, but received $contentLength"],
413,
);
}
return null;
}
}

View File

@@ -1,55 +0,0 @@
<?php
declare(strict_types=1);
namespace GraphQL\Upload;
use GraphQL\Error\Error;
use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\Node;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Utils\Utils;
use Psr\Http\Message\UploadedFileInterface;
use UnexpectedValueException;
final class UploadType extends ScalarType
{
public string $name = 'Upload';
public ?string $description
= 'The `Upload` special type represents a file to be uploaded in the same HTTP request as specified by
[graphql-multipart-request-spec](https://github.com/jaydenseric/graphql-multipart-request-spec).';
/**
* Serializes an internal value to include in a response.
*/
public function serialize(mixed $value): never
{
throw new InvariantViolation('`Upload` cannot be serialized');
}
/**
* Parses an externally provided value (query variable) to use as an input.
*/
public function parseValue(mixed $value): UploadedFileInterface
{
if (!$value instanceof UploadedFileInterface) {
throw new UnexpectedValueException('Could not get uploaded file, be sure to conform to GraphQL multipart request specification. Instead got: ' . Utils::printSafe($value));
}
$error = $value->getError();
if ($error !== UPLOAD_ERR_OK) {
throw new UploadError($error);
}
return $value;
}
/**
* Parses an externally provided literal value (hardcoded in GraphQL query) to use as an input.
*/
public function parseLiteral(Node $valueNode, ?array $variables = null): mixed
{
throw new Error('`Upload` cannot be hardcoded in query, be sure to conform to GraphQL multipart request specification. Instead got: ' . $valueNode->kind, $valueNode);
}
}

View File

@@ -1,34 +0,0 @@
<?php
declare(strict_types=1);
namespace GraphQL\Upload;
/**
* @internal
*/
final class Utility
{
public static function getPostMaxSize(): int
{
return self::fromIni('post_max_size');
}
public static function getUploadMaxFilesize(): int
{
return self::fromIni('upload_max_filesize');
}
private static function fromIni(string $key): int
{
return ini_parse_quantity(ini_get($key) ?: '0');
}
/**
* @param int|numeric-string $value
*/
public static function toMebibyte(string|int $value): string
{
return number_format($value / 1024 / 1024, 2, thousands_separator: "'") . ' MiB';
}
}