Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 1
<?php
2
 
3
/**
4
 * Slim Framework (https://slimframework.com)
5
 *
6
 * @license https://github.com/slimphp/Slim/blob/4.x/LICENSE.md (MIT License)
7
 */
8
 
9
declare(strict_types=1);
10
 
11
namespace Slim\Middleware;
12
 
13
use Psr\Http\Message\ResponseInterface;
14
use Psr\Http\Message\ServerRequestInterface;
15
use Psr\Http\Server\MiddlewareInterface;
16
use Psr\Http\Server\RequestHandlerInterface;
17
use RuntimeException;
18
 
19
use function count;
20
use function explode;
21
use function is_array;
22
use function is_null;
23
use function is_object;
24
use function is_string;
25
use function json_decode;
26
use function libxml_clear_errors;
27
use function libxml_disable_entity_loader;
28
use function libxml_use_internal_errors;
29
use function parse_str;
30
use function simplexml_load_string;
31
use function strtolower;
32
use function trim;
33
 
34
use const LIBXML_VERSION;
35
 
36
class BodyParsingMiddleware implements MiddlewareInterface
37
{
38
    /**
39
     * @var callable[]
40
     */
41
    protected array $bodyParsers;
42
 
43
    /**
44
     * @param callable[] $bodyParsers list of body parsers as an associative array of mediaType => callable
45
     */
46
    public function __construct(array $bodyParsers = [])
47
    {
48
        $this->registerDefaultBodyParsers();
49
 
50
        foreach ($bodyParsers as $mediaType => $parser) {
51
            $this->registerBodyParser($mediaType, $parser);
52
        }
53
    }
54
 
55
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
56
    {
57
        $parsedBody = $request->getParsedBody();
58
 
59
        if (empty($parsedBody)) {
60
            $parsedBody = $this->parseBody($request);
61
            $request = $request->withParsedBody($parsedBody);
62
        }
63
 
64
        return $handler->handle($request);
65
    }
66
 
67
    /**
68
     * @param string   $mediaType A HTTP media type (excluding content-type params).
69
     * @param callable $callable  A callable that returns parsed contents for media type.
70
     */
71
    public function registerBodyParser(string $mediaType, callable $callable): self
72
    {
73
        $this->bodyParsers[$mediaType] = $callable;
74
        return $this;
75
    }
76
 
77
    /**
78
     * @param string   $mediaType A HTTP media type (excluding content-type params).
79
     */
80
    public function hasBodyParser(string $mediaType): bool
81
    {
82
        return isset($this->bodyParsers[$mediaType]);
83
    }
84
 
85
    /**
86
     * @param string    $mediaType A HTTP media type (excluding content-type params).
87
     * @throws RuntimeException
88
     */
89
    public function getBodyParser(string $mediaType): callable
90
    {
91
        if (!isset($this->bodyParsers[$mediaType])) {
92
            throw new RuntimeException('No parser for type ' . $mediaType);
93
        }
94
        return $this->bodyParsers[$mediaType];
95
    }
96
 
97
    protected function registerDefaultBodyParsers(): void
98
    {
99
        $this->registerBodyParser('application/json', static function ($input) {
100
            $result = json_decode($input, true);
101
 
102
            if (!is_array($result)) {
103
                return null;
104
            }
105
 
106
            return $result;
107
        });
108
 
109
        $this->registerBodyParser('application/x-www-form-urlencoded', static function ($input) {
110
            parse_str($input, $data);
111
            return $data;
112
        });
113
 
114
        $xmlCallable = static function ($input) {
115
            $backup = self::disableXmlEntityLoader(true);
116
            $backup_errors = libxml_use_internal_errors(true);
117
            $result = simplexml_load_string($input);
118
 
119
            self::disableXmlEntityLoader($backup);
120
            libxml_clear_errors();
121
            libxml_use_internal_errors($backup_errors);
122
 
123
            if ($result === false) {
124
                return null;
125
            }
126
 
127
            return $result;
128
        };
129
 
130
        $this->registerBodyParser('application/xml', $xmlCallable);
131
        $this->registerBodyParser('text/xml', $xmlCallable);
132
    }
133
 
134
    /**
135
     * @return null|array<mixed>|object
136
     */
137
    protected function parseBody(ServerRequestInterface $request)
138
    {
139
        $mediaType = $this->getMediaType($request);
140
        if ($mediaType === null) {
141
            return null;
142
        }
143
 
144
        // Check if this specific media type has a parser registered first
145
        if (!isset($this->bodyParsers[$mediaType])) {
146
            // If not, look for a media type with a structured syntax suffix (RFC 6839)
147
            $parts = explode('+', $mediaType);
148
            if (count($parts) >= 2) {
149
                $mediaType = 'application/' . $parts[count($parts) - 1];
150
            }
151
        }
152
 
153
        if (isset($this->bodyParsers[$mediaType])) {
154
            $body = (string)$request->getBody();
155
            $parsed = $this->bodyParsers[$mediaType]($body);
156
 
157
            if ($parsed !== null && !is_object($parsed) && !is_array($parsed)) {
158
                throw new RuntimeException(
159
                    'Request body media type parser return value must be an array, an object, or null'
160
                );
161
            }
162
 
163
            return $parsed;
164
        }
165
 
166
        return null;
167
    }
168
 
169
    /**
170
     * @return string|null The serverRequest media type, minus content-type params
171
     */
172
    protected function getMediaType(ServerRequestInterface $request): ?string
173
    {
174
        $contentType = $request->getHeader('Content-Type')[0] ?? null;
175
 
176
        if (is_string($contentType) && trim($contentType) !== '') {
177
            $contentTypeParts = explode(';', $contentType);
178
            return strtolower(trim($contentTypeParts[0]));
179
        }
180
 
181
        return null;
182
    }
183
 
184
    protected static function disableXmlEntityLoader(bool $disable): bool
185
    {
186
        if (LIBXML_VERSION >= 20900) {
187
            // libxml >= 2.9.0 disables entity loading by default, so it is
188
            // safe to skip the real call (deprecated in PHP 8).
189
            return true;
190
        }
191
 
192
        // @codeCoverageIgnoreStart
193
        return libxml_disable_entity_loader($disable);
194
        // @codeCoverageIgnoreEnd
195
    }
196
}