Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
 
3
declare(strict_types=1);
4
 
5
namespace GuzzleHttp\Psr7;
6
 
7
use Psr\Http\Message\MessageInterface;
8
use Psr\Http\Message\StreamInterface;
9
 
10
/**
11
 * Trait implementing functionality common to requests and responses.
12
 */
13
trait MessageTrait
14
{
1441 ariadna 15
    /** @var string[][] Map of all registered headers, as original name => array of values */
1 efrain 16
    private $headers = [];
17
 
1441 ariadna 18
    /** @var string[] Map of lowercase header name => original name at registration */
19
    private $headerNames = [];
1 efrain 20
 
21
    /** @var string */
22
    private $protocol = '1.1';
23
 
24
    /** @var StreamInterface|null */
25
    private $stream;
26
 
27
    public function getProtocolVersion(): string
28
    {
29
        return $this->protocol;
30
    }
31
 
32
    public function withProtocolVersion($version): MessageInterface
33
    {
34
        if ($this->protocol === $version) {
35
            return $this;
36
        }
37
 
38
        $new = clone $this;
39
        $new->protocol = $version;
1441 ariadna 40
 
1 efrain 41
        return $new;
42
    }
43
 
44
    public function getHeaders(): array
45
    {
46
        return $this->headers;
47
    }
48
 
49
    public function hasHeader($header): bool
50
    {
51
        return isset($this->headerNames[strtolower($header)]);
52
    }
53
 
54
    public function getHeader($header): array
55
    {
56
        $header = strtolower($header);
57
 
58
        if (!isset($this->headerNames[$header])) {
59
            return [];
60
        }
61
 
62
        $header = $this->headerNames[$header];
63
 
64
        return $this->headers[$header];
65
    }
66
 
67
    public function getHeaderLine($header): string
68
    {
69
        return implode(', ', $this->getHeader($header));
70
    }
71
 
72
    public function withHeader($header, $value): MessageInterface
73
    {
74
        $this->assertHeader($header);
75
        $value = $this->normalizeHeaderValue($value);
76
        $normalized = strtolower($header);
77
 
78
        $new = clone $this;
79
        if (isset($new->headerNames[$normalized])) {
80
            unset($new->headers[$new->headerNames[$normalized]]);
81
        }
82
        $new->headerNames[$normalized] = $header;
83
        $new->headers[$header] = $value;
84
 
85
        return $new;
86
    }
87
 
88
    public function withAddedHeader($header, $value): MessageInterface
89
    {
90
        $this->assertHeader($header);
91
        $value = $this->normalizeHeaderValue($value);
92
        $normalized = strtolower($header);
93
 
94
        $new = clone $this;
95
        if (isset($new->headerNames[$normalized])) {
96
            $header = $this->headerNames[$normalized];
97
            $new->headers[$header] = array_merge($this->headers[$header], $value);
98
        } else {
99
            $new->headerNames[$normalized] = $header;
100
            $new->headers[$header] = $value;
101
        }
102
 
103
        return $new;
104
    }
105
 
106
    public function withoutHeader($header): MessageInterface
107
    {
108
        $normalized = strtolower($header);
109
 
110
        if (!isset($this->headerNames[$normalized])) {
111
            return $this;
112
        }
113
 
114
        $header = $this->headerNames[$normalized];
115
 
116
        $new = clone $this;
117
        unset($new->headers[$header], $new->headerNames[$normalized]);
118
 
119
        return $new;
120
    }
121
 
122
    public function getBody(): StreamInterface
123
    {
124
        if (!$this->stream) {
125
            $this->stream = Utils::streamFor('');
126
        }
127
 
128
        return $this->stream;
129
    }
130
 
131
    public function withBody(StreamInterface $body): MessageInterface
132
    {
133
        if ($body === $this->stream) {
134
            return $this;
135
        }
136
 
137
        $new = clone $this;
138
        $new->stream = $body;
1441 ariadna 139
 
1 efrain 140
        return $new;
141
    }
142
 
143
    /**
1441 ariadna 144
     * @param (string|string[])[] $headers
1 efrain 145
     */
146
    private function setHeaders(array $headers): void
147
    {
148
        $this->headerNames = $this->headers = [];
149
        foreach ($headers as $header => $value) {
150
            // Numeric array keys are converted to int by PHP.
151
            $header = (string) $header;
152
 
153
            $this->assertHeader($header);
154
            $value = $this->normalizeHeaderValue($value);
155
            $normalized = strtolower($header);
156
            if (isset($this->headerNames[$normalized])) {
157
                $header = $this->headerNames[$normalized];
158
                $this->headers[$header] = array_merge($this->headers[$header], $value);
159
            } else {
160
                $this->headerNames[$normalized] = $header;
161
                $this->headers[$header] = $value;
162
            }
163
        }
164
    }
165
 
166
    /**
167
     * @param mixed $value
168
     *
169
     * @return string[]
170
     */
171
    private function normalizeHeaderValue($value): array
172
    {
173
        if (!is_array($value)) {
174
            return $this->trimAndValidateHeaderValues([$value]);
175
        }
176
 
177
        if (count($value) === 0) {
178
            throw new \InvalidArgumentException('Header value can not be an empty array.');
179
        }
180
 
181
        return $this->trimAndValidateHeaderValues($value);
182
    }
183
 
184
    /**
185
     * Trims whitespace from the header values.
186
     *
187
     * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field.
188
     *
189
     * header-field = field-name ":" OWS field-value OWS
190
     * OWS          = *( SP / HTAB )
191
     *
192
     * @param mixed[] $values Header values
193
     *
194
     * @return string[] Trimmed header values
195
     *
1441 ariadna 196
     * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4
1 efrain 197
     */
198
    private function trimAndValidateHeaderValues(array $values): array
199
    {
200
        return array_map(function ($value) {
201
            if (!is_scalar($value) && null !== $value) {
202
                throw new \InvalidArgumentException(sprintf(
203
                    'Header value must be scalar or null but %s provided.',
204
                    is_object($value) ? get_class($value) : gettype($value)
205
                ));
206
            }
207
 
208
            $trimmed = trim((string) $value, " \t");
209
            $this->assertValue($trimmed);
210
 
211
            return $trimmed;
212
        }, array_values($values));
213
    }
214
 
215
    /**
1441 ariadna 216
     * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
1 efrain 217
     *
218
     * @param mixed $header
219
     */
220
    private function assertHeader($header): void
221
    {
222
        if (!is_string($header)) {
223
            throw new \InvalidArgumentException(sprintf(
224
                'Header name must be a string but %s provided.',
225
                is_object($header) ? get_class($header) : gettype($header)
226
            ));
227
        }
228
 
1441 ariadna 229
        if (!preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', $header)) {
1 efrain 230
            throw new \InvalidArgumentException(
1441 ariadna 231
                sprintf('"%s" is not valid header name.', $header)
1 efrain 232
            );
233
        }
234
    }
235
 
236
    /**
1441 ariadna 237
     * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
1 efrain 238
     *
239
     * field-value    = *( field-content / obs-fold )
240
     * field-content  = field-vchar [ 1*( SP / HTAB ) field-vchar ]
241
     * field-vchar    = VCHAR / obs-text
242
     * VCHAR          = %x21-7E
243
     * obs-text       = %x80-FF
244
     * obs-fold       = CRLF 1*( SP / HTAB )
245
     */
246
    private function assertValue(string $value): void
247
    {
248
        // The regular expression intentionally does not support the obs-fold production, because as
249
        // per RFC 7230#3.2.4:
250
        //
251
        // A sender MUST NOT generate a message that includes
252
        // line folding (i.e., that has any field-value that contains a match to
253
        // the obs-fold rule) unless the message is intended for packaging
254
        // within the message/http media type.
255
        //
256
        // Clients must not send a request with line folding and a server sending folded headers is
257
        // likely very rare. Line folding is a fairly obscure feature of HTTP/1.1 and thus not accepting
258
        // folding is not likely to break any legitimate use case.
1441 ariadna 259
        if (!preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/D', $value)) {
260
            throw new \InvalidArgumentException(
261
                sprintf('"%s" is not valid header value.', $value)
262
            );
1 efrain 263
        }
264
    }
265
}