Failed Conditions
Push — master ( bea035...793b2f )
by Adrien
17:10 queued 55s
created

UploadMiddleware::process()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 8
nc 3
nop 2
dl 0
loc 15
ccs 9
cts 9
cp 1
crap 3
rs 10
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Upload;
6
7
use GraphQL\Error\InvariantViolation;
8
use GraphQL\Server\RequestError;
9
use GraphQL\Utils\Utils;
10
use Laminas\Diactoros\Response\JsonResponse;
11
use Psr\Http\Message\ResponseInterface;
12
use Psr\Http\Message\ServerRequestInterface;
13
use Psr\Http\Server\MiddlewareInterface;
14
use Psr\Http\Server\RequestHandlerInterface;
15
16
class UploadMiddleware implements MiddlewareInterface
17
{
18 14
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
19
    {
20 14
        $contentType = $request->getHeader('content-type')[0] ?? '';
21
22 14
        if (mb_stripos($contentType, 'multipart/form-data') !== false) {
23 12
            $error = $this->postMaxSizeError($request);
24 12
            if ($error) {
25 1
                return $error;
26
            }
27
28 11
            $this->validateParsedBody($request);
29 8
            $request = $this->parseUploadedFiles($request);
30
        }
31
32 6
        return $handler->handle($request);
33
    }
34
35
    /**
36
     * Inject uploaded files defined in the 'map' key into the 'variables' key.
37
     */
38 8
    private function parseUploadedFiles(ServerRequestInterface $request): ServerRequestInterface
39
    {
40
        /** @var string[] $bodyParams */
41 8
        $bodyParams = $request->getParsedBody();
42
43 8
        $map = $this->decodeArray($bodyParams, 'map');
44 5
        $result = $this->decodeArray($bodyParams, 'operations');
45
46 5
        $uploadedFiles = $request->getUploadedFiles();
47 5
        foreach ($map as $fileKey => $locations) {
48 3
            foreach ($locations as $location) {
49 3
                $items = &$result;
50 3
                foreach (explode('.', $location) as $key) {
51 3
                    if (!isset($items[$key]) || !is_array($items[$key])) {
52 3
                        $items[$key] = [];
53
                    }
54 3
                    $items = &$items[$key];
55
                }
56
57 3
                if (!array_key_exists($fileKey, $uploadedFiles)) {
58 1
                    throw new RequestError(
59 1
                        "GraphQL query declared an upload in `$location`, but no corresponding file were actually uploaded",
60 1
                    );
61
                }
62
63 3
                $items = $uploadedFiles[$fileKey];
64
            }
65
        }
66
67 4
        return $request
68 4
            ->withHeader('content-type', 'application/json')
69 4
            ->withParsedBody($result);
70
    }
71
72
    /**
73
     * Validates that the request meet our expectations.
74
     */
75 11
    private function validateParsedBody(ServerRequestInterface $request): void
76
    {
77 11
        $bodyParams = $request->getParsedBody();
78
79 11
        if (null === $bodyParams) {
80 1
            throw new InvariantViolation(
81 1
                'PSR-7 request is expected to provide parsed body for "multipart/form-data" requests but got null',
82 1
            );
83
        }
84
85 10
        if (!is_array($bodyParams)) {
86 1
            throw new RequestError(
87 1
                'GraphQL Server expects JSON object or array, but got ' . Utils::printSafeJson($bodyParams),
88 1
            );
89
        }
90
91 9
        if (empty($bodyParams)) {
92 1
            throw new InvariantViolation(
93 1
                'PSR-7 request is expected to provide parsed body for "multipart/form-data" requests but got empty array',
94 1
            );
95
        }
96
    }
97
98
    /**
99
     * @param string[] $bodyParams
100
     *
101
     * @return string[][]
102
     */
103 8
    private function decodeArray(array $bodyParams, string $key): array
104
    {
105 8
        if (!isset($bodyParams[$key])) {
106 1
            throw new RequestError("The request must define a `$key`");
107
        }
108
109 7
        $value = json_decode($bodyParams[$key], true);
110 7
        if (!is_array($value)) {
111 2
            throw new RequestError("The `$key` key must be a JSON encoded array");
112
        }
113
114 5
        return $value;
115
    }
116
117 12
    private function postMaxSizeError(ServerRequestInterface $request): ?ResponseInterface
118
    {
119 12
        $contentLength = $request->getServerParams()['CONTENT_LENGTH'] ?? 0;
120 12
        $postMaxSize = Utility::getPostMaxSize();
121 12
        if ($contentLength && $contentLength > $postMaxSize) {
122 1
            $contentLength = Utility::toMebibyte($contentLength);
123 1
            $postMaxSize = Utility::toMebibyte($postMaxSize);
124
125 1
            return new JsonResponse(
126 1
                ['message' => "The server `post_max_size` is configured to accept $postMaxSize, but received $contentLength"],
127 1
                413,
128 1
            );
129
        }
130
131 11
        return null;
132
    }
133
}
134